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:
Flavio Ceolin 2021-05-18 17:53:33 -07:00 committed by Anas Nashif
commit 4c16d391c7
6 changed files with 380 additions and 0 deletions

View 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})

View 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

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

View 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;
};

View 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);
}

View 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