From 0a3f7f529bcdc28f969b4c23c620439303d5b78d Mon Sep 17 00:00:00 2001 From: Loic Domaigne Date: Wed, 25 Jun 2025 09:18:14 +0200 Subject: [PATCH 1/2] kernel: fix error propagation for device deferred initialization This fix makes sure that do_device_init() returns a negative value if the device's initialization failed. Previously, it mistakely returned +errno instead of -errno. This oversight happened during the refactoring of z_sys_init_run_level() to support deferred initialization, from which most of do_device_init() code derives. The rc value computed and stored in dev->state->init_res is the POSITIVE value of the resulting errno. Returning rc therefore breaks the convention of a negative value to signal failure. Signed-off-by: Loic Domaigne --- kernel/init.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/kernel/init.c b/kernel/init.c index dd9f3d7258f58..b004eea2d5880 100644 --- a/kernel/init.c +++ b/kernel/init.c @@ -313,13 +313,24 @@ static int do_device_init(const struct device *dev) if (dev->ops.init != NULL) { rc = dev->ops.init(dev); - /* Mark device initialized. If initialization - * failed, record the error condition. + /* If initialization failed, record in dev->state->init_res + * the POSITIVE value of the resulting errno */ if (rc != 0) { + /* device's init function should return: + * 0 on success + * a negative value on failure (-errno) + * errno value maps to an uint8_t range as of now. + */ + __ASSERT(rc >= -UINT8_MAX && rc < 0, "device %s init: invalid error (%d)", + dev->name, rc); + if (rc < 0) { rc = -rc; } + /* handle error value overflow in production + * this is likely a bug in the device's init function. Signals it + */ if (rc > UINT8_MAX) { rc = UINT8_MAX; } @@ -327,6 +338,7 @@ static int do_device_init(const struct device *dev) } } + /* device initialization has been invoked */ dev->state->initialized = true; if (rc == 0) { @@ -334,7 +346,10 @@ static int do_device_init(const struct device *dev) (void)pm_device_runtime_auto_enable(dev); } - return rc; + /* here, the value of rc is either 0 or +errno + * flip the sign to return a negative value on failure as expected + */ + return -rc; } /** From d7e8792649bf98b54e2a057f06e92d4282ddc36e Mon Sep 17 00:00:00 2001 From: Loic Domaigne Date: Wed, 25 Jun 2025 09:18:29 +0200 Subject: [PATCH 2/2] tests: kernel: device: Add tests for deferred init failure These tests verify the expected returned value and device state for error cases (-EALREADY, -errno) conform to the device_init() documentation. Signed-off-by: Loic Domaigne --- tests/kernel/device/app.overlay | 7 ++++++ tests/kernel/device/src/main.c | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/kernel/device/app.overlay b/tests/kernel/device/app.overlay index ac77f271cc553..e15aa106e3773 100644 --- a/tests/kernel/device/app.overlay +++ b/tests/kernel/device/app.overlay @@ -66,6 +66,13 @@ zephyr,deferred-init; }; + fakedeferdriver@F9000000 { + compatible = "fakedeferdriver"; + reg = <0xF9000000 0x2000>; + status = "okay"; + zephyr,deferred-init; + }; + fakedomain_0: fakedomain_0 { compatible = "fakedomain"; status = "okay"; diff --git a/tests/kernel/device/src/main.c b/tests/kernel/device/src/main.c index 2479a366ec9e5..7afd19c229d54 100644 --- a/tests/kernel/device/src/main.c +++ b/tests/kernel/device/src/main.c @@ -24,6 +24,7 @@ #define FAKEDEFERDRIVER0 DEVICE_DT_GET(DT_PATH(fakedeferdriver_e7000000)) #define FAKEDEFERDRIVER1 DEVICE_DT_GET(DT_PATH(fakedeferdriver_e8000000)) +#define FAKEDEFERDRIVER2 DEVICE_DT_GET(DT_PATH(fakedeferdriver_f9000000)) /* A device without init call */ DEVICE_DEFINE(dummy_noinit, DUMMY_NOINIT, NULL, NULL, NULL, NULL, @@ -39,6 +40,12 @@ DEVICE_DT_DEFINE(DT_INST(1, fakedeferdriver), NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &fakedeferdriverapi); +/* fake devices used to test deferred initialization failure */ +static int fakedeferdriver_init(const struct device *dev); + +DEVICE_DT_DEFINE(DT_INST(2, fakedeferdriver), fakedeferdriver_init, NULL, NULL, NULL, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL); + /** * @brief Test cases to verify device objects * @@ -415,6 +422,38 @@ ZTEST(device, test_deferred_init) zassert_true(device_is_ready(FAKEDEFERDRIVER0)); } +static int fakedeferdriver_init(const struct device *dev) +{ + return -EIO; +} + +/** + * @brief Test deferred initialization error + * + * @details Verify device_init error cases and expected device states + * + * - case -errno: if the device initialization fails + * - case -EALREADY: if the device is already initialized. + * + * @see device_init + * @ingroup kernel_device_tests + */ +ZTEST(device, test_deferred_init_failure) +{ + int ret; + const struct device *dev = FAKEDEFERDRIVER2; + + zassert_false(device_is_ready(dev)); + ret = device_init(dev); + zassert_equal(ret, -EIO); + zassert_false(device_is_ready(dev)); + zassert_equal(dev->state->init_res, EIO); + + ret = device_init(dev); + zassert_equal(ret, -EALREADY); + zassert_equal(dev->state->init_res, EIO); +} + ZTEST(device, test_device_api) { const struct device *dev;