You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mynewt.apache.org by Christopher Collins <cc...@apache.org> on 2016/12/16 21:50:02 UTC

Replacing sysinit

Hello all,

Sysinit is the process Mynewt uses to initialize all the packages in a
project.  Typically, sysinit gets invoked at the very start of main().

There are a few aspects of sysinit that I really dislike, and I think we
can replace it with something better.  In this email, I will (1) summarize the
mechanism as it exists today, (2) describe the problems with the current
mechanism, and (3) propose a new solution.

(1) CURRENT SYSINIT PROCESS

    * A package's pkg.yml file optionally specifies "init_function" and
      "init_stage"
    * At build time, the newt tool generates sysinit C files which
      define a single function.  This function calls the packages' init
      functions in the correct order.
        * Stage 0 runs first, stage 1 next, etc.
        * Within a stage, initializations are ordered alphabetically by
          package name.
    * For split images, two sysinit files get generated: one for the
      loader and one for the app.  The sysinit() macro (defined in
      sys/sysinit/include/sysinit.h) ensures the correct sysinit
      function gets called, depending on whether the loader or app is
      running.

The generated sysinit files are created with the following paths:
    * bin/targets/<target>/generated/src/<target>-sysinit-loader.c
    * bin/targets/<target>/generated/src/<target>-sysinit-app.c

For non-split-images, only the "app" file is created.

Here is an example of a generated sysinit function:

    void
    sysinit_app(void)
    {
        os_init();

        /*** Stage 0 */
        /* 0.0: kernel/os */
        os_pkg_init();
        /* 0.2: sys/flash_map */
        flash_map_init();

        /*** Stage 1 */
        /* 1.0: net/nimble/transport/ram */
        ble_hci_ram_pkg_init();
        /* 1.1: sys/log */
        log_init();

        /* [...] */
    }

(2) ISSUES WITH CURRENT IMPLEMENTATION

So that is how sysinit works today.  There are two aspects of the
current mechanism that I strongly dislike:
    (1) Generated code.
    (2) C identifiers specified in YAML files.

I think everyone hates generated code, so I probably don't have to say
much about (1).  As a general principle, I think it is good to minimize
the amount of magic newt performs during builds.

The reason I dislike (2) is that it creates an opportunity for really
confusing build errors.  Newt doesn't ensure each init_function setting
specifies a real function with the correct type.  If a setting is wrong,
the user gets a linker error which points at a generated sysinit file.

(3) PROPOSED SOLUTION

    * In C code, a package optionally defines an "init entry" struct
      containing:
        o Pointer to its init function.
        o Stage number.
    * Init entry structs are placed in a special section via
      __attribute__((section(...))).
    * At startup, the sysinit() function walks the list of entries in
      the special section and calls their corresponding init functions
      in the correct order.

This solution addresses both the issues I mentioned above:
    * No more init_function or init_stage settings in pkg.yml files.
    * No generated sysinit code.  The sysinit() function is a generic
      function that exists in apache-mynewt-core.

I should mention that this solution introduces a new issue:
indeterminate initialization order within each stage.  In the current
mechanism, newt enforces a consistent ordering of packages with the same
stage (alphabetical).  In the proposed solution, packages within a stage
do not have a predictable ordering.  Presumably, the entries are
arranged in the order the linker encounters them.  I don't think this is
a huge problem, but it does mean package authors would need to be
careful when assigning stage numbers.

Here is the API that I came up with:

    /* Package initialization function. */
    typedef void sysinit_init_fn(struct sysinit_init_ctxt *ctxt);

    struct sysinit_entry {
        /* Initializes a package. */
        sysinit_init_fn *init_fn;

        /* Specifies when the init function gets called.  0=first, * 1=next, etc. */
        uint8_t stage;
    };

    struct sysinit_init_ctxt {
        /* Corresponds to the init function currently executing. */
        const struct sysinit_entry *entry;

        /* The stage that sysinit is currently processing. */
        uint8_t cur_stage;
    };

    #define SYSINIT_REGISTER_INIT(init_cb, init_stage) /* ... */

Here is a usage example.  This is the existing mgmt/newtmgr init
function modified to use the proposed mechanism.

    void
    nmgr_pkg_init(struct sysinit_init_ctxt *ctxt)
    {
        int rc;

        /* Ensure this function only gets called by sysinit. */
        SYSINIT_ASSERT_ACTIVE();

        rc = nmgr_task_init();
        SYSINIT_PANIC_ASSERT(rc == 0);
    }

    SYSINIT_REGISTER_INIT(nmgr_pkg_init, 5);

I anticipate most initialization functions would not need the ctxt
parameter.  Some functions may find it useful, though.  For example, a
package might register a function several times, specifying a different
stage with each registration.  Such a function might need to know the
current stage to know which initialization phase to execute.

Anyway, thanks for reading.  All feedback is welcom.

Chris