Skip to content

pm: device_runtime: Use its own queue #87496

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

Merged
merged 3 commits into from
May 27, 2025
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
10 changes: 10 additions & 0 deletions doc/releases/release-notes-4.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
23 changes: 20 additions & 3 deletions doc/services/pm/device_runtime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions doc/services/pm/images/devr-async-ops.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions include/zephyr/pm/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};

Expand Down
55 changes: 55 additions & 0 deletions subsys/pm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 51 additions & 1 deletion subsys/pm/device_runtime.c
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -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)

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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 */
1 change: 1 addition & 0 deletions tests/subsys/pm/device_runtime_api/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 20 additions & 3 deletions tests/subsys/pm/device_runtime_api/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
#include <zephyr/pm/device_runtime.h>

#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);

Expand All @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions tests/subsys/pm/device_runtime_api/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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