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] Deferred device initialization #67335

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions dts/bindings/base/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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().
44 changes: 43 additions & 1 deletion include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);

/**
* @}
*/
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions include/zephyr/linker/common-rom/common-rom-kernel-devices.ld
Original file line number Diff line number Diff line change
Expand Up @@ -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 = .;
Comment on lines +21 to +23
Copy link
Member

@gmarull gmarull Jan 11, 2024

Choose a reason for hiding this comment

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

why doesn't this use its own section using eg ITERABLE_SECTION_ROM?

} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)

ITERABLE_SECTION_ROM_NUMERIC(device, 4)
Expand Down
83 changes: 59 additions & 24 deletions kernel/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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);


Expand Down
14 changes: 14 additions & 0 deletions tests/kernel/device/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@
status = "okay";
};

fakedeferdriver@E7000000 {
compatible = "fakedeferdriver";
reg = <0xE7000000 0x2000>;
status = "okay";
zephyr,deferred-init;
};

fakedeferdriver@E8000000 {
compatible = "fakedeferdriver";
reg = <0xE8000000 0x2000>;
status = "okay";
zephyr,deferred-init;
};

fakedomain_0: fakedomain_0 {
compatible = "fakedomain";
status = "okay";
Expand Down
14 changes: 14 additions & 0 deletions tests/kernel/device/boards/hifive_unmatched.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@
status = "okay";
};

fakedeferdriver@E7000000 {
compatible = "fakedeferdriver";
reg = <0x0 0xE7000000 0x0 0x2000>;
status = "okay";
zephyr,deferred-init;
};

fakedeferdriver@E8000000 {
compatible = "fakedeferdriver";
reg = <0x0 0xE8000000 0x0 0x2000>;
status = "okay";
zephyr,deferred-init;
};

fakedomain_0: fakedomain_0 {
compatible = "fakedomain";
status = "okay";
Expand Down
8 changes: 8 additions & 0 deletions tests/kernel/device/dts/bindings/fakedeferdriver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

description: Properties for fake deferred driver.

compatible: "fakedeferdriver"

include: base.yaml
48 changes: 47 additions & 1 deletion tests/kernel/device/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/init.h>
#include <zephyr/ztest.h>
#include <zephyr/sys/printk.h>
Expand All @@ -21,10 +22,23 @@
#define MY_DRIVER_A "my_driver_A"
#define MY_DRIVER_B "my_driver_B"

#define FAKEDEFERDRIVER0 DEVICE_DT_GET(DT_PATH(fakedeferdriver_e7000000))
#define FAKEDEFERDRIVER1 DEVICE_DT_GET(DT_PATH(fakedeferdriver_e8000000))

/* A device without init call */
DEVICE_DEFINE(dummy_noinit, DUMMY_NOINIT, NULL, NULL, NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);

/* To access from userspace, the device needs an API. Use a dummy GPIO one */
static const struct gpio_driver_api fakedeferdriverapi;

/* Fake deferred devices */
DEVICE_DT_DEFINE(DT_INST(0, fakedeferdriver), NULL, NULL, NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);
DEVICE_DT_DEFINE(DT_INST(1, fakedeferdriver), NULL, NULL, NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&fakedeferdriverapi);

/**
* @brief Test cases to verify device objects
*
Expand Down Expand Up @@ -387,9 +401,41 @@ ZTEST(device, test_abstraction_driver_common)
zassert_true(baz == 2, "common API do_that fail");
}

ZTEST(device, test_deferred_init)
{
int ret;

zassert_false(device_is_ready(FAKEDEFERDRIVER0));

ret = device_init(FAKEDEFERDRIVER0);
zassert_true(ret == 0);

zassert_true(device_is_ready(FAKEDEFERDRIVER0));
}

ZTEST_USER(device, test_deferred_init_user)
{
int ret;

zassert_false(device_is_ready(FAKEDEFERDRIVER1));

ret = device_init(FAKEDEFERDRIVER1);
zassert_true(ret == 0);

zassert_true(device_is_ready(FAKEDEFERDRIVER1));
}

void *user_setup(void)
{
#ifdef CONFIG_USERSPACE
k_object_access_grant(FAKEDEFERDRIVER1, k_current_get());
#endif

return NULL;
}

/**
* @}
*/

ZTEST_SUITE(device, NULL, NULL, NULL, NULL, NULL);
ZTEST_SUITE(device, NULL, user_setup, NULL, NULL, NULL);
Loading