tests: pm: Add device_runtime test
- Test two threads changing states concurrently - Test multiple calls to get/put - Test async / sync API Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
This commit is contained in:
parent
366c6ad47b
commit
4c16d391c7
6 changed files with 380 additions and 0 deletions
9
tests/subsys/pm/device_runtime/CMakeLists.txt
Normal file
9
tests/subsys/pm/device_runtime/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2021 Intel Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(device_runtime_test)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
5
tests/subsys/pm/device_runtime/prj.conf
Normal file
5
tests/subsys/pm/device_runtime/prj.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_PM=y
|
||||
CONFIG_PM_DEVICE=y
|
||||
CONFIG_PM_DEVICE_RUNTIME=y
|
||||
CONFIG_MP_NUM_CPUS=1
|
101
tests/subsys/pm/device_runtime/src/dummy_driver.c
Normal file
101
tests/subsys/pm/device_runtime/src/dummy_driver.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/printk.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <pm/device_runtime.h>
|
||||
#include "dummy_driver.h"
|
||||
|
||||
static uint32_t device_power_state;
|
||||
|
||||
static int dummy_wait(const struct device *dev)
|
||||
{
|
||||
return pm_device_wait(dev, K_FOREVER);
|
||||
}
|
||||
|
||||
static int dummy_open(const struct device *dev)
|
||||
{
|
||||
return pm_device_get(dev);
|
||||
}
|
||||
|
||||
static int dummy_open_sync(const struct device *dev)
|
||||
{
|
||||
return pm_device_get_sync(dev);
|
||||
}
|
||||
|
||||
static int dummy_close(const struct device *dev)
|
||||
{
|
||||
return pm_device_put(dev);
|
||||
}
|
||||
|
||||
static int dummy_close_sync(const struct device *dev)
|
||||
{
|
||||
return pm_device_put_sync(dev);
|
||||
}
|
||||
|
||||
static uint32_t dummy_get_power_state(const struct device *dev)
|
||||
{
|
||||
return device_power_state;
|
||||
}
|
||||
|
||||
static int dummy_suspend(const struct device *dev)
|
||||
{
|
||||
device_power_state = PM_DEVICE_STATE_SUSPEND;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_resume_from_suspend(const struct device *dev)
|
||||
{
|
||||
device_power_state = PM_DEVICE_STATE_ACTIVE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_device_pm_ctrl(const struct device *dev,
|
||||
uint32_t ctrl_command,
|
||||
uint32_t *state, pm_device_cb cb, void *arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (ctrl_command) {
|
||||
case PM_DEVICE_STATE_SET:
|
||||
if (*state == PM_DEVICE_STATE_ACTIVE) {
|
||||
ret = dummy_resume_from_suspend(dev);
|
||||
} else {
|
||||
ret = dummy_suspend(dev);
|
||||
}
|
||||
break;
|
||||
case PM_DEVICE_STATE_GET:
|
||||
*state = dummy_get_power_state(dev);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
cb(dev, ret, state, arg);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dummy_driver_api funcs = {
|
||||
.open = dummy_open,
|
||||
.open_sync = dummy_open_sync,
|
||||
.close = dummy_close,
|
||||
.close_sync = dummy_close_sync,
|
||||
.wait = dummy_wait,
|
||||
};
|
||||
|
||||
int dummy_init(const struct device *dev)
|
||||
{
|
||||
pm_device_enable(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEVICE_DEFINE(dummy_driver, DUMMY_DRIVER_NAME, &dummy_init,
|
||||
dummy_device_pm_ctrl, NULL, NULL, APPLICATION,
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &funcs);
|
23
tests/subsys/pm/device_runtime/src/dummy_driver.h
Normal file
23
tests/subsys/pm/device_runtime/src/dummy_driver.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <device.h>
|
||||
#define DUMMY_DRIVER_NAME "dummy_driver"
|
||||
|
||||
typedef int (*dummy_api_open_t)(const struct device *dev);
|
||||
|
||||
typedef int (*dummy_api_close_t)(const struct device *dev);
|
||||
|
||||
typedef int (*dummy_api_wait_t)(const struct device *dev);
|
||||
|
||||
struct dummy_driver_api {
|
||||
dummy_api_open_t open;
|
||||
dummy_api_open_t open_sync;
|
||||
dummy_api_close_t close;
|
||||
dummy_api_close_t close_sync;
|
||||
dummy_api_wait_t wait;
|
||||
};
|
237
tests/subsys/pm/device_runtime/src/main.c
Normal file
237
tests/subsys/pm/device_runtime/src/main.c
Normal file
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <ztest.h>
|
||||
#include <kernel.h>
|
||||
#include <pm/pm.h>
|
||||
#include <pm/device_runtime.h>
|
||||
#include "dummy_driver.h"
|
||||
|
||||
#define MAX_TIMES 10
|
||||
#define STACKSIZE 1024
|
||||
|
||||
/* Semaphore used to synchronize thread A and thread B*/
|
||||
K_SEM_DEFINE(sem, 0, 1);
|
||||
|
||||
K_THREAD_STACK_DEFINE(threadA_stack, STACKSIZE);
|
||||
K_THREAD_STACK_DEFINE(threadB_stack, STACKSIZE);
|
||||
|
||||
static const struct device *dev;
|
||||
static struct dummy_driver_api *api;
|
||||
|
||||
static struct k_thread threadA;
|
||||
static struct k_thread threadB;
|
||||
|
||||
|
||||
void threadA_func(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ARG_UNUSED(arg1);
|
||||
ARG_UNUSED(arg2);
|
||||
ARG_UNUSED(arg3);
|
||||
|
||||
ret = api->open(dev);
|
||||
zassert_true(ret == 0, "Fail to get device");
|
||||
|
||||
/* Lets allow threadB run */
|
||||
k_sem_give(&sem);
|
||||
|
||||
/* Block waiting for device operation conclude */
|
||||
ret = api->wait(dev);
|
||||
zassert_true(ret == 0, "Fail to wait transaction");
|
||||
|
||||
/* At this point threadB should have put the device and
|
||||
* the current state should be SUSPENDED.
|
||||
*/
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
|
||||
k_sem_take(&sem, K_FOREVER);
|
||||
|
||||
ret = api->open(dev);
|
||||
zassert_true(ret == 0, "Fail to get device");
|
||||
|
||||
/* Lets allow threadB run */
|
||||
k_sem_give(&sem);
|
||||
|
||||
ret = api->wait(dev);
|
||||
zassert_true(ret == 0, "Fail to wait transaction");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_ACTIVE, "Wrong state");
|
||||
}
|
||||
|
||||
void threadB_func(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ARG_UNUSED(arg1);
|
||||
ARG_UNUSED(arg2);
|
||||
ARG_UNUSED(arg3);
|
||||
|
||||
k_sem_take(&sem, K_FOREVER);
|
||||
|
||||
api->close(dev);
|
||||
|
||||
k_sem_give(&sem);
|
||||
ret = api->wait(dev);
|
||||
zassert_true(ret == 0, "Fail to wait transaction");
|
||||
|
||||
/* Check the state */
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief test device runtime concurrency
|
||||
*
|
||||
* @details
|
||||
* - Two threads will do different operations on a device. ThreadA will
|
||||
* try to bring up the device using an async call and then will be scheduled
|
||||
* out and threadB will run. ThreadB then will suspend the device and give up
|
||||
* in favor of threadA. At this point the device should reflect these
|
||||
* operations and be suspended.
|
||||
*
|
||||
* @see pm_device_get(), pm_device_put()
|
||||
*
|
||||
* @ingroup power_tests
|
||||
*/
|
||||
void test_concurrency(void)
|
||||
{
|
||||
k_thread_start(&threadA);
|
||||
k_thread_start(&threadB);
|
||||
|
||||
k_thread_join(&threadA, K_FOREVER);
|
||||
k_thread_join(&threadB, K_FOREVER);
|
||||
}
|
||||
|
||||
void test_setup(void)
|
||||
{
|
||||
dev = device_get_binding(DUMMY_DRIVER_NAME);
|
||||
api = (struct dummy_driver_api *)dev->api;
|
||||
|
||||
k_thread_create(&threadA, threadA_stack,
|
||||
K_THREAD_STACK_SIZEOF(threadA_stack),
|
||||
threadA_func, NULL, NULL, NULL,
|
||||
K_PRIO_PREEMPT(1), 0, K_FOREVER);
|
||||
|
||||
/* Lets make threadB has higher priority than the workqueue
|
||||
* used on device_runtime
|
||||
*/
|
||||
k_thread_create(&threadB, threadB_stack,
|
||||
K_THREAD_STACK_SIZEOF(threadB_stack),
|
||||
threadB_func, NULL, NULL, NULL,
|
||||
K_HIGHEST_THREAD_PRIO, 0, K_FOREVER);
|
||||
}
|
||||
|
||||
void test_teardown(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_ACTIVE, "Wrong state");
|
||||
|
||||
ret = api->close_sync(dev);
|
||||
zassert_true(ret == 0, "Fail to suspend device");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief test device runtime sync API
|
||||
*
|
||||
* @details
|
||||
* - Just bring up and put down the device using the synchronous API.
|
||||
*
|
||||
* @see pm_device_get_sync(), pm_device_put_sync()
|
||||
*
|
||||
* @ingroup power_tests
|
||||
*/
|
||||
void test_sync(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = api->open_sync(dev);
|
||||
zassert_true(ret == 0, "Fail to bring up device");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_ACTIVE, "Wrong state");
|
||||
|
||||
|
||||
ret = api->close_sync(dev);
|
||||
zassert_true(ret == 0, "Fail to suspend device");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief test device runtime async API with multiple calls to check
|
||||
* if the reference count keeps consistent.
|
||||
*
|
||||
* @ingroup power_tests
|
||||
*/
|
||||
void test_multiple_times(void)
|
||||
{
|
||||
int ret;
|
||||
uint8_t i;
|
||||
|
||||
/* First do it synchronously */
|
||||
for (i = 0; i < MAX_TIMES; i++) {
|
||||
ret = api->open_sync(dev);
|
||||
zassert_true(ret == 0, "Fail to bring up device");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_ACTIVE, "Wrong state");
|
||||
|
||||
|
||||
ret = api->close_sync(dev);
|
||||
zassert_true(ret == 0, "Fail to suspend device");
|
||||
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
}
|
||||
|
||||
/* Now do all requests for get and then all for put*/
|
||||
for (i = 0; i < MAX_TIMES; i++) {
|
||||
ret = api->open(dev);
|
||||
zassert_true(ret == 0, "Fail to bring up device");
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_TIMES; i++) {
|
||||
ret = api->close(dev);
|
||||
zassert_true(ret == 0, "Fail to suspend device");
|
||||
}
|
||||
|
||||
ret = api->wait(dev);
|
||||
zassert_true(ret == 0, "Fail to wait transaction");
|
||||
|
||||
/* Check the state */
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_SUSPEND, "Wrong state");
|
||||
|
||||
/* Finally off by one to keep the device active*/
|
||||
for (i = 0; i < MAX_TIMES; i++) {
|
||||
ret = api->open(dev);
|
||||
zassert_true(ret == 0, "Fail to bring up device");
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_TIMES - 1; i++) {
|
||||
ret = api->close(dev);
|
||||
zassert_true(ret == 0, "Fail to suspend device");
|
||||
}
|
||||
|
||||
ret = api->wait(dev);
|
||||
zassert_true(ret == 0, "Fail to wait transaction");
|
||||
|
||||
/* Check the state */
|
||||
zassert_true(dev->pm->state == PM_DEVICE_STATE_ACTIVE, "Wrong state");
|
||||
|
||||
}
|
||||
|
||||
void test_main(void)
|
||||
{
|
||||
ztest_test_suite(device_runtime_test,
|
||||
ztest_unit_test_setup_teardown(test_concurrency,
|
||||
test_setup,
|
||||
test_teardown),
|
||||
ztest_unit_test(test_sync),
|
||||
ztest_unit_test(test_multiple_times));
|
||||
ztest_run_test_suite(device_runtime_test);
|
||||
}
|
5
tests/subsys/pm/device_runtime/testcase.yaml
Normal file
5
tests/subsys/pm/device_runtime/testcase.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
tests:
|
||||
subsys.pm.device_pm:
|
||||
# arch_irq_unlock(0) can't work correctly on these arch
|
||||
arch_exclude: arc xtensa
|
||||
tags: power
|
Loading…
Add table
Add a link
Reference in a new issue