From 90f199b9c21f5f6d59a4720885cdb9275a292478 Mon Sep 17 00:00:00 2001 From: Ederson de Souza Date: Fri, 5 Jan 2024 13:29:20 -0800 Subject: [PATCH 1/2] 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 --- dts/bindings/base/base.yaml | 6 ++ include/zephyr/device.h | 44 +++++++++- .../common-rom/common-rom-kernel-devices.ld | 3 + kernel/init.c | 83 +++++++++++++------ 4 files changed, 111 insertions(+), 25 deletions(-) diff --git a/dts/bindings/base/base.yaml b/dts/bindings/base/base.yaml index f1d1109a19921e..b2e670fb5d81b5 100644 --- a/dts/bindings/base/base.yaml +++ b/dts/bindings/base/base.yaml @@ -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(). diff --git a/include/zephyr/device.h b/include/zephyr/device.h index 607cd76f6f22ee..27086227143a6d 100644 --- a/include/zephyr/device.h +++ b/include/zephyr/device.h @@ -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. diff --git a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld index dfa19d13492ecc..47f20cb006c124 100644 --- a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld +++ b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld @@ -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) diff --git a/kernel/init.c b/kernel/init.c index fe6c176a072c0b..76a4bbe238a6ac 100644 --- a/kernel/init.c +++ b/kernel/init.c @@ -36,6 +36,7 @@ #include #include #include +#include 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 +#endif + extern void boot_banner(void); From ae1af213436a85a7ac99cb72bbce4ab198fb8b6e Mon Sep 17 00:00:00 2001 From: Ederson de Souza Date: Fri, 5 Jan 2024 13:33:25 -0800 Subject: [PATCH 2/2] tests/kernel/device: Add tests for device deferred init Ensure that devices are not ready before calling `device_init()`, but are after the call. Signed-off-by: Ederson de Souza --- tests/kernel/device/app.overlay | 14 ++++++ .../device/boards/hifive_unmatched.overlay | 14 ++++++ .../device/dts/bindings/fakedeferdriver.yml | 8 ++++ tests/kernel/device/src/main.c | 48 ++++++++++++++++++- 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/kernel/device/dts/bindings/fakedeferdriver.yml diff --git a/tests/kernel/device/app.overlay b/tests/kernel/device/app.overlay index d7426f2b6134f8..108f14bdb3ec99 100644 --- a/tests/kernel/device/app.overlay +++ b/tests/kernel/device/app.overlay @@ -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"; diff --git a/tests/kernel/device/boards/hifive_unmatched.overlay b/tests/kernel/device/boards/hifive_unmatched.overlay index 40fa5e74adb48d..05f9992d889c14 100644 --- a/tests/kernel/device/boards/hifive_unmatched.overlay +++ b/tests/kernel/device/boards/hifive_unmatched.overlay @@ -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"; diff --git a/tests/kernel/device/dts/bindings/fakedeferdriver.yml b/tests/kernel/device/dts/bindings/fakedeferdriver.yml new file mode 100644 index 00000000000000..672ed99d1d3ac0 --- /dev/null +++ b/tests/kernel/device/dts/bindings/fakedeferdriver.yml @@ -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 diff --git a/tests/kernel/device/src/main.c b/tests/kernel/device/src/main.c index 4c02a52ce94ed9..437f2ae056c746 100644 --- a/tests/kernel/device/src/main.c +++ b/tests/kernel/device/src/main.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -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 * @@ -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);