[RFC] device: devices no longer depend on SYS_INIT

Background
----------

Nowadays, devices rely on the `init.h` (i.e. `SYS_INIT`) infrastructure
to get initialized automatically. The `init.h` infrastructure is a basic
mechanism that allows to register an init function when the system is
initialized (before `main`). It provides with multiple initialization
levels: `PRE_KERNEL_*`, `POST_KERNEL`, etc., and within each level a
numeric priority (0-99). Using this information, each registered init
entry is sorted by the linker so that the Kernel can later iterate over
them in the correct order. This all sounds nice and simple, but when it
comes to devices, this mechanism has proven to be insufficient.

Before starting with the changes proposed in this patch, let's first dig
into the implementation details of the current model. When devices are
defined, using any of the `DEVICE_*DEFINE` macros, they also create an
init entry using the internal `init.h` APIs (see
`Z_DEVICE_INIT_ENTRY_DEFINE`). This entry, stores a pointer to the
device init call and to the device itself. As the reader can imagine,
this implies a coupling between `init.h` and `device.h`.  The only link
between a device and an init entry is the device pointer stored in the
init entry. This allows the Kernel init machinery to call the init
function with the right device pointer. However, there is no direct
relationship between a device and its init function, that is, `struct
device` does not keep the device init function reference. This is not a
problem nowadays, but it could be a problem if features like deferred
initialization or init/de-init have to be implemented. However, in
reality, this is a _secondary_ problem. The most problematic issue we
have today is that devices are mixed with `SYS_INIT` calls. They are all
part of the same init block, and are treated equally. So for example,
one can theoretically have a system where the init sequence can be like:

```
- PRE_KERNEL_1
  - init call 1
  - device 0
  - init call 2
  - init call 3
  - device 1
  - device 2
  - init call 4
- PRE_KERNEL_2
  - init call 5
  - device 3
  ...
```

This is problematic because:

(1) Init calls can depend on devices, but dependency is not tracked
    anywhere. So the user must check that init priorities are correct to
    avoid runtime failures.
(2) Device drivers can have multiple instances, and each instance may
    require a different set of priorities, while `SYS_INIT` calls are
    singletons.
(3) Devices don't likely need so many init priorities (`SMP`?
    `APPLICATION`?)

(1) is particularly important because init calls do not have a specific
purpose. They usage ranges from SoC init code, to system services. So
it's _unpredictable_ what can happen in there. (2) is a tangential
topic, actually not fixed by this patch, even though it helps. (3) is
more of a post-patch cleanup we need.

So, what does this patch propose...?
------------------------------------

**First, this patch is still a HACK, so please, focus on the description
rather than with the implementation**

So it's about providing devices with their own init infrastructure,
minimizing the coupling with init.h. This patch groups devices in a
separate section, but, keeps the same init levels as before. Therefore,
the list above would look like:

```
/* devices */
- PRE_KERNEL_1
  - device 0
  - device 1
  - device 2
- PRE_KERNEL_2
  - device 3
  ...

/* init calls */
- PRE_KERNEL_1
  - init call 1
  - init call 2
  - init call 3
  - init call 4
- PRE_KERNEL_2
  - init call 5
  ...
```

This means that **it is no longer possible** to mix init calls and
devices within the same level. So how does it work now? Within each
level, devices are initialized first, then init calls. It is done this
way because I believe it is init calls who depend on devices and not
vice versa. I may be wrong, and so the whole proposal would need a
rework. So the init order would look like:

```
- PRE_KERNEL_1
  - device 0
  - device 1
  - device 2
  - init call 1
  - init call 2
  - init call 3
  - init call 4
- PRE_KERNEL_2
  - device 3
  - init call 5
  ...
```

Why does this matter for future development?
--------------------------------------------

First, we have devices grouped at least on a level basis. This means
that we could likely start using devicetree ordinals, by reducing the
source of problems to the init level only. We can also re-think device
init levels (probably init levels as well, hey `PRE_KERNEL_1/2`). For
example, there could be pre-kernel and post-kernel devices only. The
other side effect of this change is that `struct device` stores the
device init call, so we can potentially do things like defer device
initialization or implement things like init/de-init. They all come with
their own complexity, of course, but this patch could be a step forward.

Problems
--------

This is a breaking change. Sorry, guys, another one to the list. The API
does not change, so builds should continue to work. However, the system
changes its behavior, so if anyone was relying on mixed init call/device
sequences, things will break. This is why it is important to analyze
lots of use cases before moving forward with this proposal.

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
4 files changed