Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Init entries: Automatically computing priorities #73836

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

tbursztyka
Copy link
Collaborator

@tbursztyka tbursztyka commented Jun 6, 2024

Introduction

In Zephyr, forcing an initialization order is working along 2 concepts: level and priority.

Where level is a higher order rank made of 6 pre-installed sections and priority a number between 0 and 99 made to fine tune the ordering within the selected section.

For a detailed information on these levels, please refer to https://docs.zephyrproject.org/latest/kernel/drivers/index.html#initialization-levels

Initialization entries exists in 2 types: device and system. Where device represents an instance of a hardware driver, system on the other hand can be interpreted as a software service to be started automatically at boot time.

In the end, each initialization entry gets ordered by its level and priority number as a sub-section of the level section they belong to.

This is, so far, totally fine. Except that all is hard-coded by hand, and orbiting around an abstract number: priority.

Why can this be a problem?

If an ordering issue happens, it will need to be fixed by hand. And since it is an abstract number, it may require a few trial and error.

However, thanks to DTS, we already know most of the device dependency ordering: as long as a device is described via DTS, DTS generates an ordinal representing the position of the device among the others, taking into account its dependencies. Such ordinal is already in use for fine tuning the devices ordering in sections. (see #22545 for more information on how such ordinal came into the picture)

Great, but that leaves out all devices which are not described via DTS and more importantly: system initialization entries which are completely left on the side, with no means to express any actual dependency either against devices or against other system entries. Not to mention the hard coded priority is always there even for DTS based device.

Isn't there Kconfig to help?

Most, if not all, priorities are set via Kconfig generated macros after all (CONFIG_*_PRIORITY).

However, the used CONFIG_ is most of the time a generic one (like CONFIG_KERNEL_INIT_PRIORITY_DEVICE), hoping that it fits the purpose. Getting one per-driver or system entry would just clutter Kconfig. Note as well that the term "priority" is also used for preempt/coop threads and thus all this can become quickly confusing.

And again, there is no way to describe a dependency between a device and a system entry. (There has been an attempt recently to relate DTS and Kconfig to address this issue, and this issue only: see #71470 )

Solution

What is being proposed here is:

  • to get a full dependency tree for all entries (device and system)
  • to keep components in their respective role (DTS doing what it does, same for Kconfig etc...)
  • to remove all hard-coded priorities for all entries (device and system)
  • to finally generate such priority automatically from that dependency tree.

All with the necessary flexibility and transparency to address all forseeable use-cases (DTS based devices, non-DTS based devices, system entries, ...)

Obviously at built time, before any compilation happens.

How is the solution implemented

What is going to be introduced is a script which will take advantage of both DTS and Kconfig, add its own information to generate a full dependency tree and from there: to derive the right priority for each initialization entry.

In the end, exactly like what is being found in devicetree_generated.h, zephyr will find the necessary initialization information in a generated header.

On user side, the information that will be at play are nodes described by:

  • node's name: DTS node name or DTS label or DTS node path or DTS alias/chosen or DTS compat string or Device ID or system entry name
  • dependencies: one node depending on another (a list of node names)
  • level: level can be tweaked at the node level as well

Such node information is stored in YAML format, an already widely used format within Zephyr.

The term "node" has been taken from DTS. At this stage, we are not dealing with initialization entry: these only exist in the code and in the compiled objects, thus why "entry" is not used to describe a YAML object. Each YAML object is thus a node, and such node exists to describe a dependency and/or a level change about an initialization entry.

There is no need, anywhere, to get a 1:1 mapping of initialization entries vs YAML nodes. There are 446 system entries in Zephyr (i.e. SYS_INIT() calls). There is no point in getting these 446 entries a counter part node in YAML.

The end goal is to write in YAML only the relevant information.

For the user, this also means no abstract number such as priority will ever be manipulated: only clear and human readable information about what needs what in order to function properly.

Getting to the details

Information about the initialization nodes is thus written in YAML, following a pre-determined name: fw_config.yaml. It can, as for DTS overlays, exist also board's specific ones: _fw_config.yaml.

At built time, cmake will look for these in that specific order:

  • in root dir of Zephyr
  • in arch/
  • in soc/ (if any)
  • in modules/ (if any)
  • in boards/
  • in the application root directory and it's boards directory (if any).

Relevantly found YAML files will be used, along with DTS nodes (in EDT pickled object) to recreate a dependency tree. Which tree exist under the form of a graph (the same graph library used in EDT as well). From that graph, we get the ordinal for each node which becomes - in the end - the node priority under the form of a usable macro in a generated header file.

Typically a YAML node will be like:

node_name:
    dependencies:
        - dependency_node_name_x
	- dependency_node_name_y

A realistic example could then be:

enable_shell_uart:
    dependencies:
        - uart0

enable_shell_uart is a system entry, created in subsys/shell/backends/shell_uart.c line 556. uart0 is a DTS node.

If the goal would be to get a generic description for enable_shell_uart, then there would be an issue: uart0 might be valid for one build, but not for another (could be uart1 ..) Thus, when relevant, it is possible to use DTS aliases and chosen nodes.
So above example would become:

enable_shell_uart:
    dependencies:
        - zephyr,shell-uart

Finally, this is a fully generic description of the dependency between the system entry enable_shell_uart and the device entry it requires.

Let's see a different use-case where 2 nodes are as follow:

service_boot_phase1:
    dependencies:
        - soc

service_boot_phase2:
    dependencies:
        - service_boot_phase1

By default in the code, their level of initialization has been set to PRE_KERNEL_1. Let's say now that service_boot_phase1 has to be moved on POST_KERNEL just for the current application:

service_boot_phase1:
    level: POST_KERNEL
    dependencies:
        - soc

This is entirely feasible. And generates various output:

  • service_boot_phase1 will get a dedicated explicit level: POST_KERNEL
  • service_boot_phase2 on the other hand will get a dedicated implicit level: POST_KERNEL as well, because it depends on service_boot_phase1

This level information will also generate relevant macros that will be used then, surperceeding the default hard-coded level in the code. Note however a slight difference in behavior: an explicit level will always replace the default. An implicit level on the other hand will replace the default only if it is a higher level than the default.

Wrong level vs dependencies such as:

service_boot_phase1:
    level: POST_KERNEL
    dependencies:
        - soc

service_boot_phase2:
    level: EARLY
    dependencies:
        - service_boot_phase1

will be detected and will generate an error: since service_boot_phase2 needs service_boot_phase1, which one starts at level POST_KERNEL, it is out of question that itself starts at level EARLY.

Since we are dealing with possible errors, dependency loops are also detected:

service_boot_phase1:
    dependencies:
        - soc
	- service_boot_final

service_boot_phase2:
    dependencies:
        - service_boot_phase1

service_boot_final:
    dependencies:
        - service_boot_phase2

It is an impossible dependency tree to create and will generate an error.

Optional feature

Kconfig generated options can be interpreted as well to tune level and dependencies in a node. This is a yet-to-be properly formalized, but it is already possible to affect level and dependencies depending on enabled options via Kconfig.

For instance:

uart_console_init:
    dependencies:
        - zephyr,console
    if:
        CONFIG_EARLY_CONSOLE
    then:
        level: PRE_KERNEL_1

Is valid, and will set an explicit PRE_KERNEL_1 level if CONFIG_EARLY_CONSOLE is set. (which is the exact hard-coded behavior in uart_console.c btw).

It could be made mode complicated (currently only one if/then is allowed with a minimalistic unique option treated)

This Kconfig integration feature is, I think, an interesting one but would deserve more thoughts: 2+ if/then possible, more complicated if statements like: (CONFIG_1 and CONFIG_2) or CONFIG_3 ?
Or perhaps it is a rabbit hole...

This feature could be put on the side at first, and reintroduced later if needed.

Addition of a MANUAL level

DTS, since just a short while ago, can force a initialization entry in a dedicated section which will not be treated at boot time. This is to fix an actual need, but doing it through DTS was not exactly the right way since it introduced a zephyr specific software property in DTS. (the DTS property "zephyr,deferred-init"). And DTS is only meant to describe the hardware.

Now, with what is being introduced here, it is simply fixed via adding a "MANUAL" level. It is much more zephyr-ish as it follows the already existing level section naming scheme, no new macros needed such as Z_DEFER_DEVICE_INIT_ENTRY_DEFINE() etc...

And via a fw_config.yaml, such level can be set like for instance:

can0:
    level: MANUAL

Rest is as usual. Up to the user to call the device initialization function.

Why are the files called fw_config.yaml?

It is indeed a very generic name and there is a point to it.
At first, fixing this initialization ordering issue was thought to be tackled on its own script, with its own files. Let's face it however: the place where this procedure happens is not only valid for the initialization entries, but for any type of zephyr tweaks where neither Kconfig nor DTS can have a role to play.

There is already things happening like #68127 and perhaps more to come. So instead of multiplying the files, it is more relevant to gather all of this in a central place. Whether the same script should do all - via modules - like what is being proposed here, is open to debate as well. Files could be normalized under one unique naming scheme, but totally different scripts could handle them.

What would need to be done next?

  • Marking SYS_INIT() and Z_DEVICE_INIT_ENTRY_DEFINE() macro users (DEVICE_DEFINE, DEVICE_DT_DEFINE, etc...) as obsolete in order to introduce new ones without the - now fully useless - "prio" parameter. (I already have the necessary code to do so, I just need new names. Any names idea?)
  • De-clutter Kconfig files by removing all - now fully useless - CONFIG_*_PRIORITY options (but the ones related to threads, obviously)
  • Updating headers documentation and actual documentation accordingly
  • Making DTS "zephyr,deferred-init" obsolete, and removing it in the future.
  • System entry can be set to MANUAL level as well, but unlike devices there isn't the logic to call the init entry easily. This could be added so user could also call the system entry init function wherever it is needed.
  • Reducing the number of system entries would be really great too, see Introduce platform and board hooks  #62595 for instance
  • Should we keep hard-coded dependency injection? (since adding a dependency with DTS nodes is now possible as well via fw_config.yaml files and benefit from a dependency sanity check?)

Thank to @carles and @gmarull for reviewing the first prototype and bringing constructive ideas on this.

@tbursztyka
Copy link
Collaborator Author

Example on how things are working as it should: tests/misc/check_init_priorities now fails because there is no error induced (the test is expecting an error), since the hard-coded priorities are no longer used.

This scripts does not have any meaning since priorities are not written
by hand anymore.

As long as priorities were written by hand, it was indeed required to
verify that the ordering was in par with what DTS generated in its
dependency tree. It was indeed possible to induce errors by assigning
a priority that would break this dependency ordering.

Now that all is generated automatically, hand-to-hand with DTS, it can
no longer happen.

Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@proton.me>
As for DTS generated header, unittest needs to generate an empty
fwconfig generated header.

Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@proton.me>
Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't had time for a full review. But a design whine: do we really want this in YAML?

That's literally adding a whole extra source language to produce a binary. It's no longer enough to read the C+dts+kconfig files to understand what the code is going to do, you need to find and read the yaml files too. I think that's sort of a poor trade.[1]

Can't this be done in the existing dts tree? The data structure is robust enough, it supports overlays already (though maybe not at the same granularity as the yaml file locations?). We could have a "mixin" style thing where you could annotate existing nodes with init ordering metadata, etc...

I don't really love DTS either but I do think it would be better to have one bad syntax than two different ones.

@tbursztyka
Copy link
Collaborator Author

tbursztyka commented Jun 11, 2024

@andyross

Can't this be done in the existing dts tree? The data structure is robust enough, it supports overlays already (though maybe not at the same granularity as the yaml file locations?). We could have a "mixin" style thing where you could annotate existing nodes with init ordering metadata, etc...

You mean something like: #23394 ? ;)

The issue is that DTS is only meant to describe hardware and hardware only. I recall getting a big NO about that PR in a meeting at that time due to that rule, precisely.

Unless things have changed since?

@nashif
Copy link
Member

nashif commented Jun 11, 2024

did not go deep into this, but here a few thoughts and few concerns:

  • this PR taking about init entries but ends up implementing fw configurations?
  • I understand that we are using files (yaml) to avoid parsing binaries in yet another build stage, correct?

There is no need, anywhere, to get a 1:1 mapping of initialization entries vs YAML nodes. There are 446 system entries in Zephyr (i.e. SYS_INIT() calls). There is no point in getting these 446 entries a counter part node in YAML.

This pretty much keeps SYS_INIT intact. With a change as fundemnetal as this, do we really want to bandaid the issue like this and keep SYS_INIT the same way if we had the change to actually overhaul the entire thing now?

The issue is that SYS_INIT is one of the the most abused macros which contributes to inconsistencies in the boot process already. All board/soc SYS_INIT calls can be replaced with fixed platform hooks (#62595), all kernel SYS_INIT calls can also be replaced with fixed init calls and hooks, those will never change and they are expected to be called the same way, always. using SYS_INIT is wrong (will post a PR to show how we can remove all SYS_INIT usages from kernel/). And it does not end here... If we spend some time on fixing the issue at the right place, we should be left with a small portion of the "problem" which might change how the solution would like at the end.... Also see #73908 which tries to remove usage of SYS_INIT in arch code...

Since we are dealing with possible errors, dependency loops are also detected:

service_boot_phase1:
    dependencies:
        - soc
	- service_boot_final

service_boot_phase2:
    dependencies:
        - service_boot_phase1

service_boot_final:
    dependencies:
        - service_boot_phase2

It is an impossible dependency tree to create and will generate an error.

If using a dedicated configuration file is the way to go, did you look into defining this with a file per service? for example uart_console is defined in one file and only once, let's call it a unit a la systemd, the unit defines what it requires, its type, after what is should run etc. etc. The script then looks at all "unit" files and creates the final graph based on that instead of having fw_config which tries to express everything in one file which will be a bit tricky to manage and resolve, i.e. if you have the same things define in different places....

Addition of a MANUAL level

Why do we need to add this as a level? We are trying to clean up the levels. We probably need some more levels to create some meaningful things to depend on and run in and remove some of the hacks we have now (merge PRE_KERNEL_1/_2, remove SMP) etc....
Instead of MANUAL I would rather look into declaring something as enabled/disabled, so the boot/init unit file itself shall have all the needed information, probably it will be enabled by default, if something is to be started (or enabled) manually, it would just be marked as such and will not start......

@tbursztyka
Copy link
Collaborator Author

@nashif

* this PR taking about init entries but ends up implementing fw configurations?

See description "Why are the files called fw_config.yaml?".
More build-time generated config/code may be required. It might be indeed confusing to see the introduction such central configuration tool here yes. Since it is not what the PR is trying to solve at first.
Anyway, it can still live as a standalone solution, and merging with something else later-on could occur if needed.

* I understand that we are using files (yaml) to avoid parsing binaries in yet another build stage, correct?

Resolving dependencies has to be done before compilation. DTS does already that job for all hardware devices, the problem that remained was the system entries (SYS_INIT): without a mean to describe them and the related dependencies, there is no way you can solve the whole dependency tree. YAML is already widely used in Zephyr thus the choice toward that syntax. I never looked at parsing binaries, that's really not the right path for such issue.

There is no need, anywhere, to get a 1:1 mapping of initialization entries vs YAML nodes. There are 446 system entries in Zephyr (i.e. SYS_INIT() calls). There is no point in getting these 446 entries a counter part node in YAML.

This pretty much keeps SYS_INIT intact. With a change as fundemnetal as this, do we really want to bandaid the issue like this and keep SYS_INIT the same way if we had the change to actually overhaul the entire thing now?

That's a question I asked myself as well, if all SYS_INIT entries are described (like hw devices are through DTS), we could apply way more changes then. (we could get rid of all levels for instance).
But atm, there are just too many ones. 446 . Even if the ones in soc, kernel and a few arch specific are removed, it still lets us with >150 ones.
So I came up with something in the middle instead: just automating priority calculation, tweaking the level if necessary (so keeping the default hard-coded ones). After all, nothing blocks us for improving the solution when there will be way less SYS_INITs.

The issue is that SYS_INIT is one of the the most abused macros which contributes to inconsistencies in the boot process already. All board/soc SYS_INIT calls can be replaced with fixed platform hooks (#62595), all kernel SYS_INIT calls can also be replaced with fixed init calls and hooks, those will never change and they are expected to be called the same way, always. using SYS_INIT is wrong (will post a PR to show how we can remove all SYS_INIT usages from kernel/). And it does not end here... If we spend some time on fixing the issue at the right place, we should be left with a small portion of the "problem" which might change how the solution would like at the end.... Also see #73908 which tries to remove usage of SYS_INIT in arch code...

I have been following these PR yes. (It's actually a nice coincidence it's been tackled now)
coccinelle is a nice tool for this.

If using a dedicated configuration file is the way to go, did you look into defining this with a file per service? for example uart_console is defined in one file and only once, let's call it a unit a la systemd, the unit defines what it requires, its type, after what is should run etc. etc. The script then looks at all "unit" files and creates the final graph based on that instead of having fw_config which tries to express everything in one file which will be a bit tricky to manage and resolve, i.e. if you have the same things define in different places....

I did have something like that in mind at first, but again the number of SYS_INIT everywhere was an issue.
If we had only a few of these (< 50), it could live in one location even. That would be so much simpler.

So instead I applied the rule of root file + "overlays" that can rewrite or tweak what's in the root. Doing so adds up flexibility but also a bit of complexity since you have to know where to apply a modification.

Addition of a MANUAL level

Why do we need to add this as a level? We are trying to clean up the levels. We probably need some more levels to create some meaningful things to depend on and run in and remove some of the hacks we have now (merge PRE_KERNEL_1/_2, remove SMP) etc.... Instead of MANUAL I would rather look into declaring something as enabled/disabled, so the boot/init unit file itself shall have all the needed information, probably it will be enabled by default, if something is to be started (or enabled) manually, it would just be marked as such and will not start......

enabled/disabled, sure why not (it is shorter/simpler actually), but you still need a place where to put the actual init entries.
Anyway, this manual level patch it just a by-product. Idea was to show that if you put a node to manual, though it is a dependency to others, then what do you do about these, etc... Also because current solution in DTS is not a good one.

@andyross
Copy link
Contributor

The issue is that DTS is only meant to describe hardware and hardware only. I recall getting a big NO about that PR in a meeting at that time due to that rule, precisely.

Fair enough, though in this case we're right on the boundary. And DTS as currently implemented has absolutely been inching closer to a general software configuration language (c.f. "chosen" phandles, disabled flags, @edersondisouza 's recent PRs dealing with deferred initialization). So maybe that decision might be different.[1]

Alternatively, do it in C? Write some macros that emit a init dependency record, then a python script that collates them after an early linker pass, computes the needed magic, and emits it as an ordered SYS_INIT list before the final link?

[1] For the record: I absolutely think DTS should be strictly limited to a description of the hardware on which software will be configured to run. But we walked away from that model a while ago.

@andyross
Copy link
Contributor

Actually thinking about this:

Alternatively, do it in C? Write some macros that emit a init dependency record, then a python script that collates them after an early linker pass, computes the needed magic, and emits it as an ordered SYS_INIT list before the final link?

That's not too far from the way it works now, just that the "magic" is some kludgey sorting in a linker script. You could augment SYS_INIT() to emit a more involved descriptor right were it is currently, then the script just emits the legacy SYS_INIT() records for the benefit of the final link. Though again, that sort is sort of a dirty trick and I think python could do it cleaner and better.

@tbursztyka
Copy link
Collaborator Author

@andyross

Actually thinking about this:

Alternatively, do it in C? Write some macros that emit a init dependency record, then a python script that collates them after an early linker pass, computes the needed magic, and emits it as an ordered SYS_INIT list before the final link?

That's not too far from the way it works now, just that the "magic" is some kludgey sorting in a linker script. You could augment SYS_INIT() to emit a more involved descriptor right were it is currently, then the script just emits the legacy SYS_INIT() records for the benefit of the final link. Though again, that sort is sort of a dirty trick and I think python could do it cleaner and better.

Yes I really prefer sticking with python. Easier to have a human readable description, easier to tweak... Also because it is ran before any compilation/linking happens, so it is possible to detect inconsistencies asap.
In an ideal goal, we could easily have only one parameter to SYS_INIT(): the function it calls and that's it.

@tbursztyka
Copy link
Collaborator Author

tbursztyka commented Jun 12, 2024

@nashif and it grows almost every week, we are now at 448 ones https://pastebin.com/NBQhHAX1
soc has 150, boards has 53, arch has 13, kernel has 18, if we remove the 13 in tests, in the end we are still left with 201. Some are easy to replace, some like in drivers/ethernet/eth_nxp_s32_netc_psi.c for instance, are annoying ones. It will be a tedious task to clean up all this.

@nashif
Copy link
Member

nashif commented Jun 13, 2024

one more of those #74219

@ghost
Copy link

ghost commented Jun 13, 2024

TLDR; I'd again like to point to Inversion of Control containers from the (web) application programming world as an additional source of inspiration for dependency, lifecycle and configuration management. The most well-known is the Spring IoC container but several others have proven the concept over decades.

We recently discussed a similar approach for the configuration of the network subsystem and I see similar conceptual problems in this PR. If we go down this route, then we'll soon have C+dts+kconfig+settings subsys+Zephyr specific yaml for net settings+Zephyr specific yaml for dependency management. The latter two accompanied by custom scripts that are more akin to ad-hoc band-aids than implementing battle-hardened concepts.

The big con for any new approach is that we further obfuscate dependencies through one more layer of indirection as has been pointed out. But if we have to go the way of abstraction anyway, then I propose we re-use tried and trusted, well-documented and well-understood concepts.

The IoC container concept solves the configuration, dependency and lifecycle/PM management requirements in one approach:

  • Each instance of a software unit can define their own dependencies (but doesn't have to, due to inheritance as in devicetree).
  • The problem of initialization order is solved implicitly via the dependency graph similarly to what is proposed in this PR. The dependency graph is not restricted by subsystem-specific assumptions. It can be extended to any software unit (beyond drivers).
  • Other than in systemd, each instance of a software unit can be configured individually (see the network subsys settings requirements), similarly to what -nix subsystems often do in /etc/<subsys>.d/<prio>-<instance>.ini files w/o having to define an explicit order: configuration is just another lifecycle event.
  • Additionally to initialization, dependency injection and configuration, other system events (start, stop, destroy, restart, power down/up, network down/up...) can be represented and broadcast w/o violating encapsulation, compare generic/extensible initialization levels and per-component lifecycle events in system.d.
  • Network, PM, ... events would no longer be restricted to certain types of software units and could be produced and consumed at any granularity level (system, subsystem, subsystem component, subsystem/component instance).
  • The concept decouples configuration input (e.g. yaml) and output (e.g. precompiler defines) via a single intermediate data model.
  • The intermediate data model is fully determined by the target unit, not by the system or the specific representation format. Any configuration model can be added w/o knowledge of any specific subsystem, including OOT vendor- or application-specific custom configuration, compare devicetree's binding files.
  • Configuration can be collected, combined and output with pluggable source/target adapters from any source format (including legacy SYS_INIT calls and configuration approaches but also build/variant-specific property files or similar).
  • Input and output representations will be decoupled from any usage site similarly to the decoupling achieved through devicetree.
  • We get a clean migration path by slowly moving configuration from existing configuration sources (SYS_INIT, net settings, "abused" Kconfig/devicetree, ...) to a common target representation (e.g. in YAML) while remaining backwards compatible as long as we want. Sources can be prioritized to implement multi-level fallback including system defaults.
  • Overlays, keeping configuration close to the software units concerned, conditional addition of configuration snippets, etc. are all solved nicely.

It'll still be a big challenge to adapt the concept to the embedded world, e.g. by doing most of the work before and during build, minimal RAM/ROM footprint, mapping dependency and configuration information to an easy-to-learn file hierarchy (maybe s.th. like etc/), combining pre-build, build and runtime configuration, collecting software unit instance information, integration with linker scripts, integrating learnings from devicetree, and so on.

This is obviously not a fully thought-through solution proposal. It is meant as a thought-provoking side comment, so please bear with me if I overlooked or mis-represented requirements I'm not aware of.

@nashif
Copy link
Member

nashif commented Jun 13, 2024

one more #74228

@tbursztyka
Copy link
Collaborator Author

@fgrandel

The big con for any new approach is that we further obfuscate dependencies through one more layer of indirection as has been pointed out.

How is writing dependencies in a human-readable way obfuscating things even more against arbitrary chosen hard-coded priority number? Quite the contrary, actually.

The IoC container concept solves the configuration, dependency and lifecycle/PM management requirements in one approach

You are adding way way more requirements to what is being addressed in this PR.

I want to fix things incrementally, bits by bits. Now it's the priority, and that is all.
I think I'll revert back to a standalone script with dedicated yaml files, opening it to a generic fw_config.yaml seems to trigger imagination a bit too much and makes the discussion drifting.

Side note, this PR adds a new script but it removes also another one in the process (check_init_priorities.py), so that's not much added in the build after all.

@andyross
Copy link
Contributor

it grows almost every week, we are now at 448

Right, but the overwhelming majority of those are dependency-free, and don't need any special treatment. They're just lazy drivers and subsystems that don't want to puzzle out the DTS macros, and copied an init level like "POST_KERNEL" from some other code. Seems like a little attention to those would go a long way to avoid the need for all the yaml, which still gives me an ick.

@andyross
Copy link
Contributor

Like, what if we deprecated the priority and level entirely, documented that no ordering is provided for SYS_INIT() hooks, and focused on removing/augmenting/replacing only the SYS_INIT() calls that are actually trying to do something special, so that they can get their ordering from DTS?

@tbursztyka
Copy link
Collaborator Author

tbursztyka commented Jun 14, 2024

it grows almost every week, we are now at 448

Right, but the overwhelming majority of those are dependency-free, and don't need any special treatment. They're just lazy drivers and subsystems that don't want to puzzle out the DTS macros, and copied an init level like "POST_KERNEL" from some other code. Seems like a little attention to those would go a long way to avoid the need for all the yaml, which still gives me an ick.

Yes, thus my remark on the useless 1:1 mapping of those in this pr description. And finally I got CI pass with just a very few nodes in yaml.

Like, what if we deprecated the priority and level entirely, documented that no ordering is provided for SYS_INIT() hooks, and focused on removing/augmenting/replacing only the SYS_INIT() calls that are actually trying to do something special, so that they can get their ordering from DTS?

Thus bringing more software specific stuff into DTS? Isn't this going against what we stated previously?

@carlescufi
Copy link
Member

carlescufi commented Jun 18, 2024

Architecture WG:

  • @tbursztyka presents the proposal
  • The .yaml file describes additional dependencies and ordering, but it is optional
  • @nashif describes how priorities are currently being used to actually describe priorities. For example, PRE_KERNEL_1 and PRE_KERNEL_2
  • @gmarull suggests that the .yaml actually spells out the type of DTS reference. E.g.: node_label:uart0. General agreement
  • @henrikbrixandersen mentions that this proposal does not address the fact that SYS_INIT has several drawbacks. One is the inability to check for errors in SYS_INIT calls, or even chain them together. Instead, having hooks and a new service concept that allows you to bring services up and down, check their status, etc.
  • @nashif agrees with @henrikbrixandersen regarding the use and abuse of SYS_INIT. The priority and dependency problem is just one part of the problem
  • @gmarull mentions that using code to declare dependencies is an issue, because the Devicetree files are static and known at build time
  • @tbursztyka counters that init_entry could be modified in the future to include additional members for advanced services
  • @henrikbrixandersen suggests keeping SYS_INIT alongside with the newfangled services. He also suggests keeping SYS_INIT with the priorities. Others, @nashif, @fabiobaltieri suggest getting rid of the priority instead
  • @carlescufi asks at what level the .yaml file would be injected. @tbursztyka explains that you can both override and extend already at multiple levels (root, arch, soc, modules, boards, app)
  • @nashif asks why we need multi-level injection points, especially SoC. @tbursztyka explains that, for example, for x86, there are additional dependencies to get the system to boot properly
  • @nashif suggests not to mix this .yaml with the proposed network configuration interface
  • @galak considers "subsystem" to be a good candidate to the list of injection points
  • @henrikbrixandersen states that we are moving the complexity from priorities to the files. Why does a user need to modify the dependencies. @tbursztyka counters that this is a possibility, does need to be used if it's not needed
  • @fabiobaltieri suggests pairing the design with a tool that adds visualization of the init sequence, for users as well
  • @edersondisouza asks how this works when combined with Kconfig. The system runs after both DT and Kconfig, and is capable of depending on Kconfig options
  • @galak agrees that in general the app should not need to tweak these dependencies, but it serves as a big get out of jail card if something goes wrong

@tbursztyka
Copy link
Collaborator Author

From the last meeting, now that the overall idea has been detailed and discussed, here if the changes I would already incorporate:

  • standalone script/files (so not fw_config.yaml, probably just  _init.yaml or something like that.
  • About @gmarull idea, I would go for dts_node/dts_label/dts_alias/dts_chosen as prefixes for a node identifying one from DTS. I.e:
dts_alias:zephyr,shell-uart

or just dts, it would be shorter, though less self-documenting (but do we really need to be very specific?), i.e:

dts:zephyr,shell-uart

?

Before implementing anything however, I would like to focus the next discussion on these particular elements:

  • The list of directories to look for the .yaml files as I did is fixed, since I made the files not unique to a SYS_INIT: it is a compilation of many ones at once. If I understood well what @nashif and others proposed, it would be better to have one unique file per-SYS_INIT, again only when needed (90% of sys_init do not need anything so far). @galak talked about subsys as being a better place. But - imo - that's only true for "advanced" services such as bt, usb, network stack ... Problem: we still have SYS_INIT being scattered all over the place (not to mention possible locations oot). Some are being removed (kernel, soc...), but some will stay (driver's pcie and a few others). Thus my question: wouldn't it be simpler and better to integrate with existing CMakeLists.txt (if that's possible, I did not look at the feasibility yet.)? This would also have the benefit to include only the relevant .yaml files, depending on Kconfig option.
    Could be something like (in drivers/pcie/host/CMakeLists.txt):
zephyr_init_desc_sources_ifdef(CONFIG_PCIE pcie_init.yaml)

Would that make sense?

  • I would like to hide the actual level names from the yaml file. What about something more human readable as "early-boot", "before-kernel", "after-kernel" ? The script will do the proper translation. Idea if to not bind the yaml files to the actual levels in the C code. Something then like:
uart_console_init:
  run: before-kernel

@carlescufi
Copy link
Member

Architecture WG:

  • @tbursztyka states that keeping SYS_INIT functional with priorities is very difficult because all the other priorities around it will have changed
  • @tbursztyka : Keep SYS_INIT with priority as is, but deprecated
  • @gmarull suggests to introduce a new, non-deprecated EXEC_INIT that is based on dependencies but without priority
  • A lot of preliminary work needs to be done, especially for SoCs and boards, in order to clean up the tree of
  • @tbursztyka mentions that DEVICE_DEFINE and company also use priority, so a similar approach needs to be used

@nashif
Copy link
Member

nashif commented Jul 3, 2024

@tbursztyka can you post the cocci script you mentioned somewhere?

@tbursztyka
Copy link
Collaborator Author

@tbursztyka can you post the cocci script you mentioned somewhere?

I uploaded to you in discord, did you get it?

@carlescufi
Copy link
Member

Architecture WG:

  • @nashif mentions that this should be declared or defined by the services, and so this has no overlap with the problem described here: [RFC] Integrated Boot Configuration System (build, provisioning, runtime) #76902
  • @bjarki-andreasen states the opposite: anything that is declared by the user or developer as static, multi-instance structure data to be used by the firmware should use the same format
  • @nashif is against a single configuration .yaml file, especially for the init subsystem. Instead, there would need to be files for each service, and a way for the user to override specific dependencies or priorities

@tbursztyka
Copy link
Collaborator Author

Closing it as this superseded by #79340

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Base OS Base OS Library (lib/os) area: Build System area: Device Model area: Devicetree area: IPM Inter-Processor Mailbox area: Kernel area: Linker Scripts area: Networking area: X86 x86 Architecture (32-bit) DNM This PR should not be merged (Do Not Merge)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants