kernel: Device deferred initialization

Currently, all devices are initialized at boot time (following their
level and priority order). This patch introduces deferred
initialization: by setting the property `zephyr,deferred-init` on a
device on the devicetree, Zephyr will not initialized the device.

To initialize such devices, one has to call `device_init()`.

Deferred initialization is done by grouping all deferred devices on a
different ELF section. In this way, there's no need to consume more
memory to keep track of deferred devices. When `device_init()` is
called, Zephyr will scan the deferred devices section and call the
initialization function for the matching device. As this scanning is
done only during deferred device initialization, its cost should be
bearable.

Signed-off-by: Ederson de Souza <ederson.desouza@intel.com>
This commit is contained in:
Ederson de Souza 2024-01-05 13:29:20 -08:00 committed by Anas Nashif
parent eaa2c60220
commit eeebb4d911
4 changed files with 111 additions and 25 deletions

View File

@ -92,3 +92,9 @@ properties:
mbox-names:
type: string-array
description: Provided names of mailbox / IPM channel specifiers
zephyr,deferred-init:
type: boolean
description: |
Do not initialize device automatically on boot. Device should be manually
initialized using device_init().

View File

@ -151,6 +151,16 @@ typedef int16_t device_handle_t;
#define DEVICE_DT_NAME(node_id) \
DT_PROP_OR(node_id, label, DT_NODE_FULL_NAME(node_id))
/**
* @brief Determine if a devicetree node initialization should be deferred.
*
* @param node_id The devicetree node identifier.
*
* @return Boolean stating if node initialization should be deferred.
*/
#define DEVICE_DT_DEFER(node_id) \
DT_PROP(node_id, zephyr_deferred_init)
/**
* @brief Create a device object from a devicetree node identifier and set it up
* for boot time initialization.
@ -758,6 +768,22 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
return z_device_is_ready(dev);
}
/**
* @brief Initialize a device.
*
* A device whose initialization was deferred (by marking it as
* ``zephyr,deferred-init`` on devicetree) needs to be initialized manually via
* this call. Note that only devices whose initialization was deferred can be
* initialized via this call - one can not try to initialize a non
* initialization deferred device that failed initialization with this call.
*
* @param dev device to be initialized.
*
* @retval -ENOENT If device was not found - or isn't a deferred one.
* @retval -errno For other errors.
*/
__syscall int device_init(const struct device *dev);
/**
* @}
*/
@ -988,6 +1014,18 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
}, \
}
#define Z_DEFER_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn_) \
static const Z_DECL_ALIGN(struct init_entry) __used __noasan \
__attribute__((__section__(".z_deferred_init"))) \
Z_INIT_ENTRY_NAME(DEVICE_NAME_GET(dev_id)) = { \
.init_fn = {COND_CODE_1(Z_DEVICE_IS_MUTABLE(node_id), (.dev_rw), (.dev)) = \
(init_fn_)}, \
{ \
COND_CODE_1(Z_DEVICE_IS_MUTABLE(node_id), (.dev_rw), (.dev)) = \
&DEVICE_NAME_GET(dev_id), \
}, \
}
/**
* @brief Define a @ref device and all other required objects.
*
@ -1019,7 +1057,11 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, pm, data, config, level, \
prio, api, state, Z_DEVICE_DEPS_NAME(dev_id)); \
\
Z_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn, level, prio)
COND_CODE_1(DEVICE_DT_DEFER(node_id), \
(Z_DEFER_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, \
init_fn)), \
(Z_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn, \
level, prio)));
/**
* @brief Declare a device for each status "okay" devicetree node.

View File

@ -18,6 +18,9 @@
CREATE_OBJ_LEVEL(init, APPLICATION)
CREATE_OBJ_LEVEL(init, SMP)
__init_end = .;
__deferred_init_list_start = .;
KEEP(*(.z_deferred_init))
__deferred_init_list_end = .;
} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
ITERABLE_SECTION_ROM_NUMERIC(device, 4)

View File

@ -36,6 +36,7 @@
#include <zephyr/timing/timing.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/internal/syscall_handler.h>
LOG_MODULE_REGISTER(os, CONFIG_KERNEL_LOG_LEVEL);
BUILD_ASSERT(CONFIG_MP_NUM_CPUS == CONFIG_MP_MAX_NUM_CPUS,
@ -301,6 +302,37 @@ extern volatile uintptr_t __stack_chk_guard;
__pinned_bss
bool z_sys_post_kernel;
static int do_device_init(const struct init_entry *entry)
{
const struct device *dev = entry->dev;
int rc = 0;
if (entry->init_fn.dev != NULL) {
rc = entry->init_fn.dev(dev);
/* Mark device initialized. If initialization
* failed, record the error condition.
*/
if (rc != 0) {
if (rc < 0) {
rc = -rc;
}
if (rc > UINT8_MAX) {
rc = UINT8_MAX;
}
dev->state->init_res = rc;
}
}
dev->state->initialized = true;
if (rc == 0) {
/* Run automatic device runtime enablement */
(void)pm_device_runtime_auto_enable(dev);
}
return rc;
}
/**
* @brief Execute all the init entry initialization functions at a given level
*
@ -332,36 +364,39 @@ static void z_sys_init_run_level(enum init_level level)
const struct device *dev = entry->dev;
if (dev != NULL) {
int rc = 0;
if (entry->init_fn.dev != NULL) {
rc = entry->init_fn.dev(dev);
/* Mark device initialized. If initialization
* failed, record the error condition.
*/
if (rc != 0) {
if (rc < 0) {
rc = -rc;
}
if (rc > UINT8_MAX) {
rc = UINT8_MAX;
}
dev->state->init_res = rc;
}
}
dev->state->initialized = true;
if (rc == 0) {
/* Run automatic device runtime enablement */
(void)pm_device_runtime_auto_enable(dev);
}
do_device_init(entry);
} else {
(void)entry->init_fn.sys();
}
}
}
int z_impl_device_init(const struct device *dev)
{
if (dev == NULL) {
return -ENOENT;
}
STRUCT_SECTION_FOREACH_ALTERNATE(_deferred_init, init_entry, entry) {
if (entry->dev == dev) {
return do_device_init(entry);
}
}
return -ENOENT;
}
#ifdef CONFIG_USERSPACE
static inline int z_vrfy_device_init(const struct device *dev)
{
K_OOPS(K_SYSCALL_OBJ_INIT(dev, K_OBJ_ANY));
return z_impl_device_init(dev);
}
#include <syscalls/device_init_mrsh.c>
#endif
extern void boot_banner(void);