Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2020 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <sys/printk.h>
#include <zephyr.h>
#include <zephyr/types.h>
#include <ztest.h>
#include <ksched.h>
#include <kernel.h>
#include <power/power.h>
#include "dummy_driver.h"

#define SLEEP_MSEC 100
#define SLEEP_TIMEOUT K_MSEC(SLEEP_MSEC)

/* for checking power suspend and resume order between system and devices */
static bool enter_low_power;
static bool notify_app_entry;
static bool notify_app_exit;
static bool set_pm;
static bool leave_idle;
static bool idle_entered;

static const struct device *dev;
static struct dummy_driver_api *api;
/*
 * Weak power hook functions. Used on systems that have not implemented
 * power management.
 */
__weak void pm_power_state_set(struct pm_state_info info)
{
	/* at this point, notify_pm_state_entry() implemented in
	 * this file has been called and set_pm should have been set
	 */
	zassert_true(set_pm == true,
		     "Notification to enter suspend was not sent to the App");

	/* this function is called after devices enter low power state */
	uint32_t device_power_state;
	/* at this point, devices have been deactivated */
	device_get_power_state(dev, &device_power_state);
	zassert_false(device_power_state == DEVICE_PM_ACTIVE_STATE, NULL);

	/* this function is called when system entering low power state, so
	 * parameter state should not be POWER_STATE_ACTIVE
	 */
	zassert_false(info.state == PM_STATE_ACTIVE,
		      "Entering low power state with a wrong parameter");
}

__weak void pm_power_state_exit_post_ops(struct pm_state_info info)
{
	/* pm_system_suspend is entered with irq locked
	 * unlock irq before leave pm_system_suspend
	 */
	irq_unlock(0);
}

__weak bool pm_policy_low_power_devices(enum pm_state state)
{
	return pm_is_sleep_state(state);
}

/* Our PM policy handler */
struct pm_state_info pm_policy_next_state(int ticks)
{
	struct pm_state_info info = {};

	/* make sure this is idle thread */
	zassert_true(z_is_idle_thread_object(_current), NULL);
	zassert_true(ticks == _kernel.idle, NULL);
	idle_entered = true;

	if (enter_low_power) {
		enter_low_power = false;
		notify_app_entry = true;
		info.state = PM_STATE_RUNTIME_IDLE;
	} else {
		/* only test pm_policy_next_state()
		 * no PM operation done
		 */
		info.state = PM_STATE_ACTIVE;
	}
	return info;
}

/* implement in application, called by idle thread */
static void notify_pm_state_entry(enum pm_state state)
{
	uint32_t device_power_state;

	/* enter suspend */
	zassert_true(notify_app_entry == true,
		     "Notification to enter suspend was not sent to the App");
	zassert_true(z_is_idle_thread_object(_current), NULL);
	zassert_equal(state, PM_STATE_RUNTIME_IDLE, NULL);

	/* at this point, devices are active */
	device_get_power_state(dev, &device_power_state);
	zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL);
	set_pm = true;
	notify_app_exit = true;
}

/* implement in application, called by idle thread */
static void notify_pm_state_exit(enum pm_state state)
{
	uint32_t device_power_state;

	/* leave suspend */
	zassert_true(notify_app_exit == true,
		     "Notification to leave suspend was not sent to the App");
	zassert_true(z_is_idle_thread_object(_current), NULL);
	zassert_equal(state, PM_STATE_RUNTIME_IDLE, NULL);

	/* at this point, devices are active again*/
	device_get_power_state(dev, &device_power_state);
	zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL);
	leave_idle = true;

}

/*
 * @brief test power idle
 *
 * @details
 *  - The global idle routine executes when no other work is available
 *  - The idle routine provide a timeout parameter to the suspend routine
 *    indicating the amount of time guaranteed to expire before the next
 *    timeout, pm_policy_next_state() handle this parameter.
 *  - In this case, pm_policy_next_sate() return POWER_STATE_ACTIVE,
 *    so there is no low power operation happen.
 *
 * @see pm_policy_next_state()
 *
 * @ingroup power_tests
 */
void test_power_idle(void)
{
	TC_PRINT("give way to idle thread\n");
	k_sleep(SLEEP_TIMEOUT);
	zassert_true(idle_entered, "Never entered idle thread");
}

/*
 * @brief test power state transition
 *
 * @details
 *  - The system support control of power state ordering between
 *    subsystems and devices
 *  - The application can control system power state transitions in idle thread
 *    through pm_notify_pm_state_entry and pm_notify_pm_state_exit
 *
 * @see pm_notify_pm_state_entry(), pm_notify_pm_state_exit()
 *
 * @ingroup power_tests
 */
void test_power_state_trans(void)
{
	enter_low_power = true;
	/* give way to idle thread */
	k_sleep(SLEEP_TIMEOUT);
	zassert_true(leave_idle, NULL);
}

/*
 * @brief notification between system and device
 *
 * @details
 *  - device driver notify its power state change by device_pm_get and
 *    device_pm_put
 *  - system inform device system power state change through device interface
 *    device_pm_control
 *
 * @see device_pm_get(), device_pm_put(), device_set_power_state(),
 *      device_get_power_state()
 *
 * @ingroup power_tests
 */
void test_power_state_notification(void)
{
	uint32_t device_power_state;

	device_get_power_state(dev, &device_power_state);
	zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL);

	api->close(dev);
	device_get_power_state(dev, &device_power_state);
	zassert_equal(device_power_state, DEVICE_PM_SUSPEND_STATE, NULL);
	/* reopen device as it will be closed in teardown */
	api->open(dev);
}

void test_setup(void)
{
	int ret;

	dev = device_get_binding(DUMMY_DRIVER_NAME);
	api = (struct dummy_driver_api *)dev->api;
	ret = api->open(dev);
	zassert_true(ret == 0, "Fail to open device");
}

void test_teardown(void)
{
	api->close(dev);
}

void test_main(void)
{
	struct pm_notifier notifier = {
		.state_entry = notify_pm_state_entry,
		.state_exit = notify_pm_state_exit,
	};

	pm_notifier_register(&notifier);

	ztest_test_suite(power_management_test,
			 ztest_1cpu_unit_test(test_power_idle),
			 ztest_unit_test_setup_teardown(test_power_state_trans,
							test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(
						test_power_state_notification,
						test_setup,
						test_teardown));
	ztest_run_test_suite(power_management_test);
	pm_notifier_unregister(&notifier);
}