diff --git a/doc/services/pm/device_runtime.rst b/doc/services/pm/device_runtime.rst index 5d9df2d8706e79..b0dd57db02e16f 100644 --- a/doc/services/pm/device_runtime.rst +++ b/doc/services/pm/device_runtime.rst @@ -224,5 +224,5 @@ asynchronous API: ... /* "put" device (decreases usage count, schedule suspend if no more users) */ - return pm_device_runtime_put_async(dev); + return pm_device_runtime_put_async(dev, K_NO_WAIT); } diff --git a/include/zephyr/pm/device_runtime.h b/include/zephyr/pm/device_runtime.h index 09bf0c04278e47..3a320b9ccbca19 100644 --- a/include/zephyr/pm/device_runtime.h +++ b/include/zephyr/pm/device_runtime.h @@ -131,6 +131,7 @@ int pm_device_runtime_put(const struct device *dev); * @funcprops \pre_kernel_ok, \async, \isr_ok * * @param dev Device instance. + * @param delay Minimum amount of time before triggering the action. * * @retval 0 If it succeeds. In case device runtime PM is not enabled or not * available this function will be a no-op and will also return 0. @@ -140,7 +141,7 @@ int pm_device_runtime_put(const struct device *dev); * * @see pm_device_runtime_put() */ -int pm_device_runtime_put_async(const struct device *dev); +int pm_device_runtime_put_async(const struct device *dev, k_timeout_t delay); /** * @brief Check if device runtime is enabled for a given device. @@ -188,9 +189,11 @@ static inline int pm_device_runtime_put(const struct device *dev) return 0; } -static inline int pm_device_runtime_put_async(const struct device *dev) +static inline int pm_device_runtime_put_async(const struct device *dev, + k_timeout_t delay) { ARG_UNUSED(dev); + ARG_UNUSED(delay); return 0; } diff --git a/include/zephyr/tracing/tracing.h b/include/zephyr/tracing/tracing.h index baf9632a30d078..f1bbddeec51436 100644 --- a/include/zephyr/tracing/tracing.h +++ b/include/zephyr/tracing/tracing.h @@ -1954,15 +1954,17 @@ /** * @brief Trace putting a device (asynchronously) call entry. * @param dev Device instance. + * @param delay Time to delay the operation */ -#define sys_port_trace_pm_device_runtime_put_async_enter(dev) +#define sys_port_trace_pm_device_runtime_put_async_enter(dev, delay) /** * @brief Trace putting a device (asynchronously) call exit. * @param dev Device instance. + * @param delay Time to delay the operation. * @param ret Return value. */ -#define sys_port_trace_pm_device_runtime_put_async_exit(dev, ret) +#define sys_port_trace_pm_device_runtime_put_async_exit(dev, delay, ret) /** * @brief Trace enabling device runtime PM call entry. diff --git a/soc/arm/st_stm32/stm32f4/Kconfig.defconfig.series b/soc/arm/st_stm32/stm32f4/Kconfig.defconfig.series index 28ed9cc3a55564..067dcfc003f36d 100644 --- a/soc/arm/st_stm32/stm32f4/Kconfig.defconfig.series +++ b/soc/arm/st_stm32/stm32f4/Kconfig.defconfig.series @@ -19,6 +19,6 @@ config TASK_WDT_HW_FALLBACK_DELAY config PM select COUNTER - select COUNTER_RTC_STM32_SUBSECONDS + select COUNTER_RTC_STM32_SUBSECONDS if DT_HAS_ST_STM32_RTC_ENABLED endif # SOC_SERIES_STM32F4X diff --git a/subsys/pm/device_runtime.c b/subsys/pm/device_runtime.c index dbb19c6926bd50..5ecfc28142a9ae 100644 --- a/subsys/pm/device_runtime.c +++ b/subsys/pm/device_runtime.c @@ -35,6 +35,7 @@ LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL); * * @param dev Device instance. * @param async Perform operation asynchronously. + * @param delay Period to delay the asynchronous operation. * * @retval 0 If device has been suspended or queued for suspend. * @retval -EALREADY If device is already suspended (can only happen if get/put @@ -42,7 +43,8 @@ LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL); * @retval -EBUSY If the device is busy. * @retval -errno Other negative errno, result of the action callback. */ -static int runtime_suspend(const struct device *dev, bool async) +static int runtime_suspend(const struct device *dev, bool async, + k_timeout_t delay) { int ret = 0; struct pm_device *pm = dev->pm; @@ -77,7 +79,7 @@ static int runtime_suspend(const struct device *dev, bool async) if (async && !k_is_pre_kernel()) { /* queue suspend */ pm->state = PM_DEVICE_STATE_SUSPENDING; - (void)k_work_schedule(&pm->work, K_NO_WAIT); + (void)k_work_schedule(&pm->work, delay); } else { /* suspend now */ ret = pm->action_cb(pm->dev, PM_DEVICE_ACTION_SUSPEND); @@ -177,8 +179,22 @@ int pm_device_runtime_get(const struct device *dev) pm->usage++; + /* + * Check if the device has a pending suspend operation (not started + * yet) and cancel it. This way we avoid unnecessary operations because + * the device is actually active. + */ + if ((pm->state == PM_DEVICE_STATE_SUSPENDING) && + ((k_work_cancel_delayable(&pm->work) & K_WORK_RUNNING) == 0)) { + pm->state = PM_DEVICE_STATE_ACTIVE; + goto unlock; + } + if (!k_is_pre_kernel()) { - /* wait until possible async suspend is completed */ + /* + * If the device is already suspending there is + * nothing else we can do but wait until it finishes. + */ while (pm->state == PM_DEVICE_STATE_SUSPENDING) { k_sem_give(&pm->lock); @@ -221,7 +237,7 @@ int pm_device_runtime_put(const struct device *dev) } SYS_PORT_TRACING_FUNC_ENTER(pm, device_runtime_put, dev); - ret = runtime_suspend(dev, false); + ret = runtime_suspend(dev, false, K_NO_WAIT); /* * Now put the domain @@ -235,7 +251,7 @@ int pm_device_runtime_put(const struct device *dev) return ret; } -int pm_device_runtime_put_async(const struct device *dev) +int pm_device_runtime_put_async(const struct device *dev, k_timeout_t delay) { int ret; @@ -243,9 +259,9 @@ int pm_device_runtime_put_async(const struct device *dev) return 0; } - SYS_PORT_TRACING_FUNC_ENTER(pm, device_runtime_put_async, dev); - ret = runtime_suspend(dev, true); - SYS_PORT_TRACING_FUNC_EXIT(pm, device_runtime_put_async, dev, ret); + SYS_PORT_TRACING_FUNC_ENTER(pm, device_runtime_put_async, dev, delay); + ret = runtime_suspend(dev, true, delay); + SYS_PORT_TRACING_FUNC_EXIT(pm, device_runtime_put_async, dev, delay, ret); return ret; } diff --git a/subsys/tracing/ctf/tracing_ctf.h b/subsys/tracing/ctf/tracing_ctf.h index 6b1b03e97d00dd..986a314f915adb 100644 --- a/subsys/tracing/ctf/tracing_ctf.h +++ b/subsys/tracing/ctf/tracing_ctf.h @@ -338,8 +338,8 @@ extern "C" { #define sys_port_trace_pm_device_runtime_get_exit(dev, ret) #define sys_port_trace_pm_device_runtime_put_enter(dev) #define sys_port_trace_pm_device_runtime_put_exit(dev, ret) -#define sys_port_trace_pm_device_runtime_put_async_enter(dev) -#define sys_port_trace_pm_device_runtime_put_async_exit(dev, ret) +#define sys_port_trace_pm_device_runtime_put_async_enter(dev, delay) +#define sys_port_trace_pm_device_runtime_put_async_exit(dev, delay, ret) #define sys_port_trace_pm_device_runtime_enable_enter(dev) #define sys_port_trace_pm_device_runtime_enable_exit(dev, ret) #define sys_port_trace_pm_device_runtime_disable_enter(dev) diff --git a/subsys/tracing/sysview/SYSVIEW_Zephyr.txt b/subsys/tracing/sysview/SYSVIEW_Zephyr.txt index dfad10e404ae8a..758023d19f7016 100644 --- a/subsys/tracing/sysview/SYSVIEW_Zephyr.txt +++ b/subsys/tracing/sysview/SYSVIEW_Zephyr.txt @@ -164,7 +164,7 @@ TaskState 0xBF 1=dummy, 2=Waiting, 4=New, 8=Terminated, 16=Suspended, 32=Termina 156 pm_system_suspend ticks=%u | Returns %Bool 157 pm_device_runtime_get dev=%I | Returns %u 158 pm_device_runtime_put dev=%I | Returns %u -159 pm_device_runtime_put_async dev=%I | Returns %u +159 pm_device_runtime_put_async dev=%I, Delay=%TimeOut | Returns %u 160 pm_device_runtime_enable dev=%I | Returns %u 161 pm_device_runtime_disable dev=%I | Returns %u diff --git a/subsys/tracing/sysview/tracing_sysview.h b/subsys/tracing/sysview/tracing_sysview.h index 105cfb1a3ee0f5..8d6e90666f5ef4 100644 --- a/subsys/tracing/sysview/tracing_sysview.h +++ b/subsys/tracing/sysview/tracing_sysview.h @@ -640,10 +640,10 @@ void sys_trace_k_thread_info(struct k_thread *thread); #define sys_port_trace_pm_device_runtime_put_exit(dev, ret) \ SEGGER_SYSVIEW_RecordEndCallU32(TID_PM_DEVICE_RUNTIME_PUT, \ (uint32_t)ret) -#define sys_port_trace_pm_device_runtime_put_async_enter(dev) \ +#define sys_port_trace_pm_device_runtime_put_async_enter(dev, delay) \ SEGGER_SYSVIEW_RecordU32(TID_PM_DEVICE_RUNTIME_PUT_ASYNC, \ - (uint32_t)(uintptr_t)dev) -#define sys_port_trace_pm_device_runtime_put_async_exit(dev, ret) \ + (uint32_t)(uintptr_t)dev, (uint32_t)delay.ticks) +#define sys_port_trace_pm_device_runtime_put_async_exit(dev, delay, ret) \ SEGGER_SYSVIEW_RecordEndCallU32(TID_PM_DEVICE_RUNTIME_PUT_ASYNC, \ (uint32_t)ret) #define sys_port_trace_pm_device_runtime_enable_enter(dev) \ diff --git a/subsys/tracing/test/tracing_test.h b/subsys/tracing/test/tracing_test.h index 6cf3ee6e5831ee..71b63a762c59c8 100644 --- a/subsys/tracing/test/tracing_test.h +++ b/subsys/tracing/test/tracing_test.h @@ -442,8 +442,8 @@ #define sys_port_trace_pm_device_runtime_get_exit(dev, ret) #define sys_port_trace_pm_device_runtime_put_enter(dev) #define sys_port_trace_pm_device_runtime_put_exit(dev, ret) -#define sys_port_trace_pm_device_runtime_put_async_enter(dev) -#define sys_port_trace_pm_device_runtime_put_async_exit(dev, ret) +#define sys_port_trace_pm_device_runtime_put_async_enter(dev, delay) +#define sys_port_trace_pm_device_runtime_put_async_exit(dev, delay, ret) #define sys_port_trace_pm_device_runtime_enable_enter(dev) #define sys_port_trace_pm_device_runtime_enable_exit(dev, ret) #define sys_port_trace_pm_device_runtime_disable_enter(dev) diff --git a/subsys/tracing/user/tracing_user.h b/subsys/tracing/user/tracing_user.h index ddc6668d68a022..c4231503d70a09 100644 --- a/subsys/tracing/user/tracing_user.h +++ b/subsys/tracing/user/tracing_user.h @@ -329,8 +329,8 @@ void sys_trace_idle(void); #define sys_port_trace_pm_device_runtime_get_exit(dev, ret) #define sys_port_trace_pm_device_runtime_put_enter(dev) #define sys_port_trace_pm_device_runtime_put_exit(dev, ret) -#define sys_port_trace_pm_device_runtime_put_async_enter(dev) -#define sys_port_trace_pm_device_runtime_put_async_exit(dev, ret) +#define sys_port_trace_pm_device_runtime_put_async_enter(dev, delay) +#define sys_port_trace_pm_device_runtime_put_async_exit(dev, delay, ret) #define sys_port_trace_pm_device_runtime_enable_enter(dev) #define sys_port_trace_pm_device_runtime_enable_exit(dev, ret) #define sys_port_trace_pm_device_runtime_disable_enter(dev) diff --git a/tests/subsys/pm/device_runtime_api/src/main.c b/tests/subsys/pm/device_runtime_api/src/main.c index 57fd7d8bcc8b3f..2288d64675aeed 100644 --- a/tests/subsys/pm/device_runtime_api/src/main.c +++ b/tests/subsys/pm/device_runtime_api/src/main.c @@ -42,7 +42,7 @@ void test_api_setup(void *data) zassert_equal(ret, 0); ret = pm_device_runtime_put(test_dev); zassert_equal(ret, 0); - ret = pm_device_runtime_put_async(test_dev); + ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT); zassert_equal(ret, 0); /* enable runtime PM */ @@ -138,7 +138,7 @@ ZTEST(device_runtime_api, test_api) test_driver_pm_async(test_dev); /* usage: 1, -1, suspend: yes (queued) */ - ret = pm_device_runtime_put_async(test_dev); + ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT); zassert_equal(ret, 0); (void)pm_device_state_get(test_dev, &state); @@ -149,7 +149,7 @@ ZTEST(device_runtime_api, test_api) zassert_equal(ret, -EALREADY); /* usage: 0, -1, suspend: no (unbalanced call) */ - ret = pm_device_runtime_put_async(test_dev); + ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT); zassert_equal(ret, -EALREADY); /* unblock test driver and let it finish */ @@ -171,7 +171,7 @@ ZTEST(device_runtime_api, test_api) test_driver_pm_async(test_dev); /* usage: 1, -1, suspend: yes (queued) */ - ret = pm_device_runtime_put_async(test_dev); + ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT); zassert_equal(ret, 0); (void)pm_device_state_get(test_dev, &state); @@ -200,6 +200,47 @@ ZTEST(device_runtime_api, test_api) (void)pm_device_state_get(test_dev, &state); zassert_equal(state, PM_DEVICE_STATE_ACTIVE); + /* Test if getting a device before an async operation starts does + * not trigger any device pm action. + */ + size_t count = test_driver_pm_count(test_dev); + + ret = pm_device_runtime_put_async(test_dev, K_MSEC(10)); + zassert_equal(ret, 0); + + (void)pm_device_state_get(test_dev, &state); + zassert_equal(state, PM_DEVICE_STATE_SUSPENDING); + + ret = pm_device_runtime_get(test_dev); + zassert_equal(ret, 0); + + /* Now lets check if the calls above have triggered a device + * pm action + */ + zassert_equal(count, test_driver_pm_count(test_dev)); + + /* + * test if async put with a delay respects the given time. + */ + ret = pm_device_runtime_put_async(test_dev, K_MSEC(100)); + + (void)pm_device_state_get(test_dev, &state); + zassert_equal(state, PM_DEVICE_STATE_SUSPENDING); + + k_sleep(K_MSEC(80)); + + /* It should still be suspending since we have waited less than + * the delay we've set. + */ + (void)pm_device_state_get(test_dev, &state); + zassert_equal(state, PM_DEVICE_STATE_SUSPENDING); + + k_sleep(K_MSEC(30)); + + /* Now it should be already suspended */ + (void)pm_device_state_get(test_dev, &state); + zassert_equal(state, PM_DEVICE_STATE_SUSPENDED); + /* Put operation should fail due the state be locked. */ ret = pm_device_runtime_disable(test_dev); zassert_equal(ret, 0); diff --git a/tests/subsys/pm/device_runtime_api/src/test_driver.c b/tests/subsys/pm/device_runtime_api/src/test_driver.c index cb91cbffc0e534..d7c06f9cddb0c0 100644 --- a/tests/subsys/pm/device_runtime_api/src/test_driver.c +++ b/tests/subsys/pm/device_runtime_api/src/test_driver.c @@ -10,6 +10,7 @@ #include struct test_driver_data { + size_t count; bool ongoing; bool async; struct k_sem sync; @@ -29,6 +30,8 @@ static int test_driver_action(const struct device *dev, data->ongoing = false; + data->count++; + return 0; } @@ -53,6 +56,13 @@ bool test_driver_pm_ongoing(const struct device *dev) return data->ongoing; } +size_t test_driver_pm_count(const struct device *dev) +{ + struct test_driver_data *data = dev->data; + + return data->count; +} + int test_driver_init(const struct device *dev) { struct test_driver_data *data = dev->data; diff --git a/tests/subsys/pm/device_runtime_api/src/test_driver.h b/tests/subsys/pm/device_runtime_api/src/test_driver.h index a5305f938c453a..4979c7d2cb1bb9 100644 --- a/tests/subsys/pm/device_runtime_api/src/test_driver.h +++ b/tests/subsys/pm/device_runtime_api/src/test_driver.h @@ -36,4 +36,13 @@ void test_driver_pm_done(const struct device *dev); */ bool test_driver_pm_ongoing(const struct device *dev); +/** + * @brief Gets the number of times the device changed state. + * + * @param dev Device instance. + * + * @return The number of state changes the device made. + */ +size_t test_driver_pm_count(const struct device *dev); + #endif /* TESTS_SUBSYS_PM_DEVICE_RUNTIME_TEST_DRIVER_H_ */