diff --git a/doc/releases/release-notes-4.2.rst b/doc/releases/release-notes-4.2.rst index 136fa85d4cbfa..5a4bc0a5d7f67 100644 --- a/doc/releases/release-notes-4.2.rst +++ b/doc/releases/release-notes-4.2.rst @@ -173,6 +173,16 @@ New APIs and options * :kconfig:option:`CONFIG_MQTT_VERSION_5_0` +* Power management + + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_USE_SYSTEM_WQ` + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ` + * :kconfig:option:`CONFIG_PM_DEVICE_DRIVER_NEEDS_DEDICATED_WQ` + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_STACK_SIZE` + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_PRIO` + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_INIT_PRIO` + * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_ASYNC` + * Sockets * :kconfig:option:`CONFIG_NET_SOCKETS_INET_RAW` diff --git a/doc/services/pm/device_runtime.rst b/doc/services/pm/device_runtime.rst index 350874009b9f7..309b07ff22fe7 100644 --- a/doc/services/pm/device_runtime.rst +++ b/doc/services/pm/device_runtime.rst @@ -107,9 +107,26 @@ be a problem if the operation is fast, e.g. a register toggle. However, the situation will not be the same if suspension involves sending packets through a slow bus. For this reason the device drivers can also make use of the :c:func:`pm_device_runtime_put_async` function. This function will schedule -the suspend operation, again, if device is no longer used. The suspension will -then be carried out when the system work queue gets the chance to run. The -sequence diagram shown below illustrates this scenario. +the suspend operation, again, if device is no longer used. + + +By default, runtime PM operations are offloaded to the system work queue. +However, device drivers must not perform any blocking operations during suspend, as +this can stall the system work queue and negatively impact system responsiveness. + +To address this, applications can configure runtime PM to use a dedicated work queue +by enabling :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ`. + +If blocking behavior is required—for example, when accessing a slow peripheral +or waiting for a bus transaction—the PM subsystem work queue must be used instead. +Drivers that require this behavior can explicitly request it by enabling +:kconfig:option:`CONFIG_PM_DEVICE_DRIVER_NEEDS_DEDICATED_WQ`. + +For targets with constrained resources that do not need asynchronous +operations, this functionality can be disabled altogether by +de-selecting :kconfig:option:`CONFIOG_PM_DEVICE_RUNTIME_ASYNC`, reducing +memory usage and system complexity. + .. figure:: images/devr-async-ops.svg diff --git a/doc/services/pm/images/devr-async-ops.svg b/doc/services/pm/images/devr-async-ops.svg index 8fe1f6e18239c..9cd8a02d8053b 100644 --- a/doc/services/pm/images/devr-async-ops.svg +++ b/doc/services/pm/images/devr-async-ops.svg @@ -1,4 +1,4 @@ -ApplicationApplicationDeviceDevicePM SubsystemPM SubsystemSystem WorkqueueSystem Workqueueoperation(dev)pm_device_runtime_get(dev)Increase usagealt[usage == 1]PM_DEVICE_ACTION_RESUMEOperationpm_device_runtime_put_async(dev)Decrease usagealt[usage == 0]Schedule suspendalt[Scheduled suspend]PM_DEVICE_ACTION_SUSPEND \ No newline at end of file +--> diff --git a/include/zephyr/pm/device.h b/include/zephyr/pm/device.h index f7c008f3cf2ce..5badda9fa68f3 100644 --- a/include/zephyr/pm/device.h +++ b/include/zephyr/pm/device.h @@ -170,8 +170,10 @@ struct pm_device { struct k_sem lock; /** Event var to listen to the sync request events */ struct k_event event; +#if defined(CONFIG_PM_DEVICE_RUNTIME_ASYNC) || defined(__DOXYGEN__) /** Work object for asynchronous calls */ struct k_work_delayable work; +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ #endif /* CONFIG_PM_DEVICE_RUNTIME */ }; diff --git a/subsys/pm/Kconfig b/subsys/pm/Kconfig index 19078d02b8158..c2bca1c5f04c5 100644 --- a/subsys/pm/Kconfig +++ b/subsys/pm/Kconfig @@ -121,6 +121,61 @@ config PM_DEVICE_RUNTIME enabled, devices can be suspended or resumed based on the device usage even while the CPU or system is running. +if PM_DEVICE_RUNTIME + +config PM_DEVICE_DRIVER_NEEDS_DEDICATED_WQ + bool + +config PM_DEVICE_RUNTIME_ASYNC + bool "Asynchronous device runtime power management" + default y + help + Use this option to enable support for asynchronous operation + in the power management device runtime. + +if PM_DEVICE_RUNTIME_ASYNC + +choice PM_DEVICE_RUNTIME_WQ + prompt "Work queue to be used by pm device runtime async" + default PM_DEVICE_RUNTIME_USE_DEDICATED_WQ if PM_DEVICE_DRIVER_NEEDS_DEDICATED_WQ + default PM_DEVICE_RUNTIME_USE_SYSTEM_WQ + +config PM_DEVICE_RUNTIME_USE_SYSTEM_WQ + bool "Use the system workqueue" + help + When this option is enabled the power management subsystem will + use the system worqueue instead of defining its own queue. + +config PM_DEVICE_RUNTIME_USE_DEDICATED_WQ + bool "Use a dedicated workqueue" + help + When this option is enabled the power management subsystem will + use a dedicated worqueue instead of the system work queue. + +if PM_DEVICE_RUNTIME_USE_DEDICATED_WQ +config PM_DEVICE_RUNTIME_DEDICATED_WQ_STACK_SIZE + int "Stack size for pm runtime async workqueue" + default 1024 + help + Defines the size of the stack on the workqueue used for + async operations. + +config PM_DEVICE_RUNTIME_DEDICATED_WQ_PRIO + int "PM device runtime workqueue priority. Should be pre-emptible." + default SYSTEM_WORKQUEUE_PRIORITY if PM_DEVICE_RUNTIME_USE_SYSTEM_WQ + default 0 + +config PM_DEVICE_RUNTIME_DEDICATED_WQ_INIT_PRIO + int "PM device runtime workqueue init priority" + default 50 + help + Init priority level to setup the device runtime workqueue. +endif #PM_DEVICE_RUNTIME_USE_DEDICATED_WQ +endchoice + +endif # PM_DEVICE_RUNTIME_ASYNC +endif # PM_DEVICE_RUNTIME + config PM_DEVICE_SHELL bool "Device Power Management shell" depends on SHELL diff --git a/subsys/pm/device_runtime.c b/subsys/pm/device_runtime.c index 4533886dd2af5..cf58108fea444 100644 --- a/subsys/pm/device_runtime.c +++ b/subsys/pm/device_runtime.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2018 Intel Corporation. * Copyright (c) 2021 Nordic Semiconductor ASA. + * Copyright (c) 2025 HubbleNetwork. * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +20,13 @@ LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL); #define PM_DOMAIN(_pm) NULL #endif +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC +#ifdef CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ +K_THREAD_STACK_DEFINE(pm_device_runtime_stack, CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_STACK_SIZE); +static struct k_work_q pm_device_runtime_wq; +#endif /* CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ */ +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ + #define EVENT_STATE_ACTIVE BIT(PM_DEVICE_STATE_ACTIVE) #define EVENT_STATE_SUSPENDED BIT(PM_DEVICE_STATE_SUSPENDED) @@ -78,8 +86,14 @@ static int runtime_suspend(const struct device *dev, bool async, if (async) { /* queue suspend */ +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC pm->base.state = PM_DEVICE_STATE_SUSPENDING; +#ifdef CONFIG_PM_DEVICE_RUNTIME_USE_SYSTEM_WQ (void)k_work_schedule(&pm->work, delay); +#else + (void)k_work_schedule_for_queue(&pm_device_runtime_wq, &pm->work, delay); +#endif /* CONFIG_PM_DEVICE_RUNTIME_USE_SYSTEM_WQ */ +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ } else { /* suspend now */ ret = pm->base.action_cb(pm->dev, PM_DEVICE_ACTION_SUSPEND); @@ -99,6 +113,7 @@ static int runtime_suspend(const struct device *dev, bool async, return ret; } +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC static void runtime_suspend_work(struct k_work *work) { int ret; @@ -128,6 +143,7 @@ static void runtime_suspend_work(struct k_work *work) __ASSERT(ret == 0, "Could not suspend device (%d)", ret); } +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ static int get_sync_locked(const struct device *dev) { @@ -225,6 +241,7 @@ int pm_device_runtime_get(const struct device *dev) pm->base.usage++; +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC /* * Check if the device has a pending suspend operation (not started * yet) and cancel it. This way we avoid unnecessary operations because @@ -250,6 +267,7 @@ int pm_device_runtime_get(const struct device *dev) (void)k_sem_take(&pm->lock, K_FOREVER); } } +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ if (pm->base.usage > 1U) { goto unlock; @@ -348,6 +366,7 @@ int pm_device_runtime_put(const struct device *dev) int pm_device_runtime_put_async(const struct device *dev, k_timeout_t delay) { +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC int ret; if (dev->pm_base == NULL) { @@ -368,6 +387,10 @@ int pm_device_runtime_put_async(const struct device *dev, k_timeout_t delay) SYS_PORT_TRACING_FUNC_EXIT(pm, device_runtime_put_async, dev, delay, ret); return ret; +#else + LOG_WRN("Function not available"); + return -ENOSYS; +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ } __boot_func @@ -439,7 +462,9 @@ int pm_device_runtime_enable(const struct device *dev) /* lazy init of PM fields */ if (pm->dev == NULL) { pm->dev = dev; +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC k_work_init_delayable(&pm->work, runtime_suspend_work); +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ } if (pm->base.state == PM_DEVICE_STATE_ACTIVE) { @@ -512,6 +537,7 @@ int pm_device_runtime_disable(const struct device *dev) (void)k_sem_take(&pm->lock, K_FOREVER); } +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC if (!k_is_pre_kernel()) { if ((pm->base.state == PM_DEVICE_STATE_SUSPENDING) && ((k_work_cancel_delayable(&pm->work) & K_WORK_RUNNING) == 0)) { @@ -529,6 +555,7 @@ int pm_device_runtime_disable(const struct device *dev) (void)k_sem_take(&pm->lock, K_FOREVER); } } +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ /* wake up the device if suspended */ if (pm->base.state == PM_DEVICE_STATE_SUSPENDED) { @@ -539,8 +566,9 @@ int pm_device_runtime_disable(const struct device *dev) pm->base.state = PM_DEVICE_STATE_ACTIVE; } - +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC clear_bit: +#endif atomic_clear_bit(&pm->base.flags, PM_DEVICE_FLAG_RUNTIME_ENABLED); unlock: @@ -569,3 +597,25 @@ int pm_device_runtime_usage(const struct device *dev) return dev->pm_base->usage; } + +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC +#ifdef CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ + +static int pm_device_runtime_wq_init(void) +{ + const struct k_work_queue_config cfg = {.name = "PM DEVICE RUNTIME WQ"}; + + k_work_queue_init(&pm_device_runtime_wq); + + k_work_queue_start(&pm_device_runtime_wq, pm_device_runtime_stack, + K_THREAD_STACK_SIZEOF(pm_device_runtime_stack), + CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_PRIO, &cfg); + + return 0; +} + +SYS_INIT(pm_device_runtime_wq_init, POST_KERNEL, + CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_INIT_PRIO); + +#endif /* CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ */ +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ diff --git a/tests/subsys/pm/device_runtime_api/prj.conf b/tests/subsys/pm/device_runtime_api/prj.conf index 5708c297e9dab..f76825d13483b 100644 --- a/tests/subsys/pm/device_runtime_api/prj.conf +++ b/tests/subsys/pm/device_runtime_api/prj.conf @@ -3,3 +3,4 @@ CONFIG_PM=y CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y CONFIG_MP_MAX_NUM_CPUS=1 +CONFIG_ZTEST_THREAD_PRIORITY=3 diff --git a/tests/subsys/pm/device_runtime_api/src/main.c b/tests/subsys/pm/device_runtime_api/src/main.c index ba4482ac8f300..d9f5f35aed4b6 100644 --- a/tests/subsys/pm/device_runtime_api/src/main.c +++ b/tests/subsys/pm/device_runtime_api/src/main.c @@ -9,8 +9,12 @@ #include #include "test_driver.h" +#include "zephyr/sys/util_macro.h" + static const struct device *test_dev; + +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC static struct k_thread get_runner_td; K_THREAD_STACK_DEFINE(get_runner_stack, 1024); @@ -31,6 +35,7 @@ static void get_runner(void *arg1, void *arg2, void *arg3) ret = pm_device_runtime_get(test_dev); zassert_equal(ret, 0); } +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ void test_api_setup(void *data) { @@ -43,7 +48,11 @@ void test_api_setup(void *data) ret = pm_device_runtime_put(test_dev); zassert_equal(ret, 0); ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT); +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC zassert_equal(ret, 0); +#else + zassert_equal(ret, -ENOSYS); +#endif /* enable runtime PM */ ret = pm_device_runtime_enable(test_dev); @@ -130,6 +139,7 @@ ZTEST(device_runtime_api, test_api) zassert_equal(ret, -EALREADY); zassert_equal(pm_device_runtime_usage(test_dev), 0); +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC /*** get + asynchronous put until suspended ***/ /* usage: 0, +1, resume: yes */ @@ -200,8 +210,10 @@ ZTEST(device_runtime_api, test_api) */ k_thread_create(&get_runner_td, get_runner_stack, K_THREAD_STACK_SIZEOF(get_runner_stack), get_runner, - NULL, NULL, NULL, CONFIG_SYSTEM_WORKQUEUE_PRIORITY, 0, - K_NO_WAIT); + NULL, NULL, NULL, + COND_CODE_1(CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ, + (CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_PRIO), + (CONFIG_SYSTEM_WORKQUEUE_PRIORITY)), 0, K_NO_WAIT); k_yield(); /* let driver suspend to finish and wait until get_runner finishes @@ -259,6 +271,7 @@ ZTEST(device_runtime_api, test_api) ret = pm_device_runtime_disable(test_dev); zassert_equal(ret, 0); zassert_equal(pm_device_runtime_usage(test_dev), -ENOTSUP); +#endif /* CONFIG_PM_DEVICE_RUNTIME_ASYNC */ } DEVICE_DEFINE(pm_unsupported_device, "PM Unsupported", NULL, NULL, NULL, NULL, @@ -273,7 +286,11 @@ ZTEST(device_runtime_api, test_unsupported) zassert_equal(pm_device_runtime_disable(dev), -ENOTSUP, ""); zassert_equal(pm_device_runtime_get(dev), 0, ""); zassert_equal(pm_device_runtime_put(dev), 0, ""); - zassert_false(pm_device_runtime_put_async(dev, K_NO_WAIT), ""); +#ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC + zassert_equal(pm_device_runtime_put_async(dev, K_NO_WAIT), 0, ""); +#else + zassert_equal(pm_device_runtime_put_async(dev, K_NO_WAIT), -ENOSYS, ""); +#endif } int dev_pm_control(const struct device *dev, enum pm_device_action action) diff --git a/tests/subsys/pm/device_runtime_api/testcase.yaml b/tests/subsys/pm/device_runtime_api/testcase.yaml index 80ce7bd9bebaf..b4ab9e1bd66ae 100644 --- a/tests/subsys/pm/device_runtime_api/testcase.yaml +++ b/tests/subsys/pm/device_runtime_api/testcase.yaml @@ -10,3 +10,13 @@ tests: - native_sim extra_configs: - CONFIG_TEST_PM_DEVICE_ISR_SAFE=y + pm.device_runtime.async_dedicated_wq.api: + platform_allow: + - native_sim + extra_configs: + - CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ=y + pm.device_runtime.async_disabled.api: + platform_allow: + - native_sim + extra_configs: + - CONFIG_PM_DEVICE_RUNTIME_ASYNC=n