blob: 623aa20b61235c6d73346d954cb6fabb442d91b5 [file] [log] [blame]
/*
* Copyright (c) 2021 Nordic Semiconductor ASA.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include "test_driver.h"
static const struct device *test_dev;
static struct k_thread get_runner_td;
K_THREAD_STACK_DEFINE(get_runner_stack, 1024);
static void get_runner(void *arg1, void *arg2, void *arg3)
{
int ret;
bool ongoing;
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
/* make sure we test blocking path (suspend is ongoing) */
ongoing = test_driver_pm_ongoing(test_dev);
zassert_equal(ongoing, true);
/* usage: 0, +1, resume: yes */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
}
void test_api_setup(void *data)
{
int ret;
enum pm_device_state state;
/* check API always returns 0 when runtime PM is disabled */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
ret = pm_device_runtime_put(test_dev);
zassert_equal(ret, 0);
ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT);
zassert_equal(ret, 0);
/* enable runtime PM */
ret = pm_device_runtime_enable(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDED);
/* enabling again should succeed (no-op) */
ret = pm_device_runtime_enable(test_dev);
zassert_equal(ret, 0);
}
static void test_api_teardown(void *data)
{
int ret;
enum pm_device_state state;
/* let test driver finish async PM (in case it was left pending due to
* a failure)
*/
if (test_driver_pm_ongoing(test_dev)) {
test_driver_pm_done(test_dev);
}
/* disable runtime PM, make sure device is left into active state */
ret = pm_device_runtime_disable(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_ACTIVE);
}
/**
* @brief Test the behavior of the device runtime PM API.
*
* Scenarios tested:
*
* - get + put
* - get + asynchronous put until suspended
* - get + asynchronous put + get (while suspend still ongoing)
*/
ZTEST(device_runtime_api, test_api)
{
int ret;
enum pm_device_state state;
/* device is initially suspended */
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDED);
/*** get + put ***/
/* usage: 0, +1, resume: yes */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_ACTIVE);
/* usage: 1, +1, resume: no */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
/* usage: 2, -1, suspend: no */
ret = pm_device_runtime_put(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_ACTIVE);
/* usage: 1, -1, suspend: yes */
ret = pm_device_runtime_put(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDED);
/* usage: 0, -1, suspend: no (unbalanced call) */
ret = pm_device_runtime_put(test_dev);
zassert_equal(ret, -EALREADY);
/*** get + asynchronous put until suspended ***/
/* usage: 0, +1, resume: yes */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_ACTIVE);
test_driver_pm_async(test_dev);
/* usage: 1, -1, suspend: yes (queued) */
ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT);
zassert_equal(ret, 0);
if (IS_ENABLED(CONFIG_TEST_PM_DEVICE_ISR_SAFE)) {
/* In sync mode async put is equivalent as normal put. */
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDED);
} else {
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDING);
/* usage: 0, -1, suspend: no (unbalanced call) */
ret = pm_device_runtime_put(test_dev);
zassert_equal(ret, -EALREADY);
/* usage: 0, -1, suspend: no (unbalanced call) */
ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT);
zassert_equal(ret, -EALREADY);
/* unblock test driver and let it finish */
test_driver_pm_done(test_dev);
k_yield();
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDED);
/*** get + asynchronous put + get (while suspend still ongoing) ***/
/* usage: 0, +1, resume: yes */
ret = pm_device_runtime_get(test_dev);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_ACTIVE);
test_driver_pm_async(test_dev);
/* usage: 1, -1, suspend: yes (queued) */
ret = pm_device_runtime_put_async(test_dev, K_NO_WAIT);
zassert_equal(ret, 0);
(void)pm_device_state_get(test_dev, &state);
zassert_equal(state, PM_DEVICE_STATE_SUSPENDING);
/* let suspension start */
k_yield();
/* create and start get_runner thread
* get_runner thread is used to test synchronous path while asynchronous
* is ongoing. It is important to set its priority >= to the system work
* queue to make sure sync path run by the thread is forced to wait.
*/
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);
k_yield();
/* let driver suspend to finish and wait until get_runner finishes
* resuming the driver
*/
test_driver_pm_done(test_dev);
k_thread_join(&get_runner_td, K_FOREVER);
(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);
pm_device_state_lock(test_dev);
/* This operation should not succeed. */
ret = pm_device_runtime_enable(test_dev);
zassert_equal(ret, -EPERM);
/* After unlock the state, enable runtime should work. */
pm_device_state_unlock(test_dev);
ret = pm_device_runtime_enable(test_dev);
zassert_equal(ret, 0);
}
DEVICE_DEFINE(pm_unsupported_device, "PM Unsupported", NULL, NULL, NULL, NULL,
POST_KERNEL, 0, NULL);
ZTEST(device_runtime_api, test_unsupported)
{
const struct device *const dev = DEVICE_GET(pm_unsupported_device);
zassert_false(pm_device_runtime_is_enabled(dev), "");
zassert_equal(pm_device_runtime_enable(dev), -ENOTSUP, "");
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), "");
}
int dev_pm_control(const struct device *dev, enum pm_device_action action)
{
ARG_UNUSED(dev);
ARG_UNUSED(action);
return 0;
}
PM_DEVICE_DT_DEFINE(DT_NODELABEL(test_dev), dev_pm_control);
DEVICE_DT_DEFINE(DT_NODELABEL(test_dev), NULL, PM_DEVICE_DT_GET(DT_NODELABEL(test_dev)),
NULL, NULL, POST_KERNEL, 80, NULL);
ZTEST(device_runtime_api, test_pm_device_runtime_auto)
{
const struct device *const dev = DEVICE_DT_GET(DT_NODELABEL(test_dev));
zassert_true(pm_device_runtime_is_enabled(dev), "");
zassert_equal(pm_device_runtime_get(dev), 0, "");
zassert_equal(pm_device_runtime_put(dev), 0, "");
}
void *device_runtime_api_setup(void)
{
test_dev = device_get_binding("test_driver");
zassert_not_null(test_dev, NULL);
return NULL;
}
ZTEST_SUITE(device_runtime_api, NULL, device_runtime_api_setup,
test_api_setup, test_api_teardown, NULL);