Linux Audio

Check our new training course

Loading...
/*
 * Copyright (c) 2016 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * @file
 * @brief Test early sleeping microkernel mechanism
 *
 * This test verifies that both fiber_sleep() and task_sleep()
 * can each be used to put the calling thread to sleep for a specified
 * number of ticks during system initialization (before k_server() starts)
 * as well as after the microkernel initializes (after k_server() starts).
 *
 * To ensure that the nanokernel timeout both operates correctly during
 * system initialization and that it allows fibers to sleep for a specified
 * number of ticks the test has a fiber invoke fiber_sleep() before the init
 * task invokes task_sleep(). The fiber sleep time is less than that of the
 * task sleep time so that the fiber will wake before the init task wakes.
 */

#include <zephyr.h>
#include <tc_util.h>
#include <init.h>


#define FIBER_TICKS_TO_SLEEP 40
#define TASK_TICKS_TO_SLEEP 50

/* time that the task was actually sleeping */
static int task_actual_sleep_ticks;
static int task_actual_sleep_nano_ticks;
static int task_actual_sleep_micro_ticks;
static int task_actual_sleep_app_ticks;

/* time that the fiber was actually sleeping */
static volatile int fiber_actual_sleep_ticks;

/*
 * Flag is changed by lower priority task to make sure
 * that sleeping did not go in a tight toop
 */
static bool alternate_task_run;

/* test fiber synchronization semaphore */
static struct nano_sem test_fiber_sem;

/**
 *
 * @brief Put task to sleep and measure time it really slept
 *
 * @param ticks_to_sleep number of ticks for a task to sleep
 *
 * @return number of ticks the task actually slept
 */
int test_task_sleep(int ticks_to_sleep)
{
	uint32_t start_time;
	uint32_t stop_time;

	start_time = sys_cycle_get_32();
	task_sleep(ticks_to_sleep);
	stop_time = sys_cycle_get_32();

	return (stop_time - start_time) / sys_clock_hw_cycles_per_tick;
}


/**
 *
 * @brief Put fiber to sleep and measure time it really slept
 *
 * @param ticks_to_sleep number of ticks for a fiber to sleep
 *
 * @return number of ticks the fiber actually slept
 */
int test_fiber_sleep(int ticks_to_sleep)
{
	uint32_t start_time;
	uint32_t stop_time;

	start_time = sys_cycle_get_32();
	fiber_sleep(ticks_to_sleep);
	stop_time = sys_cycle_get_32();

	return (stop_time - start_time) / sys_clock_hw_cycles_per_tick;
}


/**
 *
 * @brief Early task sleep test
 *
 * Note: it will be used to test the early sleep at SECONDARY level too
 *
 * Call task_sleep() and checks the time sleep actually
 * took to make sure that task actually slept
 *
 * @return 0
 */
static int test_early_task_sleep(struct device *unused)
{
	ARG_UNUSED(unused);
	task_actual_sleep_ticks = test_task_sleep(TASK_TICKS_TO_SLEEP);
	return 0;
}

SYS_INIT(test_early_task_sleep, SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

/**
 *
 * @brief Early task sleep test in NANOKERNEL level only
 *
 * Call task_sleep() and checks the time sleep actually
 * took to make sure that task actually slept
 *
 * @return 0
 */
static int test_early_task_sleep_in_nanokernel_level(struct device *unused)
{
	ARG_UNUSED(unused);
	task_actual_sleep_nano_ticks = test_task_sleep(TASK_TICKS_TO_SLEEP);
	return 0;
}

SYS_INIT(test_early_task_sleep_in_nanokernel_level,
	 NANOKERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

/**
 *
 * @brief Early task sleep test in MICROKERNEL level only
 *
 * Call task_sleep() and checks the time sleep actually
 * took to make sure that task actually slept
 *
 * @return 0
 */
static int test_early_task_sleep_in_microkernel_level(struct device *unused)
{
	ARG_UNUSED(unused);
	task_actual_sleep_micro_ticks = test_task_sleep(TASK_TICKS_TO_SLEEP);
	return 0;
}

SYS_INIT(test_early_task_sleep_in_microkernel_level,
	 MICROKERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

/**
 *
 * @brief Early task sleep test in APPLICATION level only
 *
 * Call task_sleep() and checks the time sleep actually
 * took to make sure that task actually slept
 *
 * @return 0
 */
static int test_early_task_sleep_in_application_level(struct device *unused)
{
	ARG_UNUSED(unused);
	task_actual_sleep_app_ticks = test_task_sleep(TASK_TICKS_TO_SLEEP);
	return 0;
}

SYS_INIT(test_early_task_sleep_in_application_level,
	 APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

/**
 *
 * @brief Fiber function that measures fiber sleep time
 *
 * @return N/A
 */
static void test_fiber(int ticks_to_sleep, int unused)
{
	ARG_UNUSED(unused);
	while (1) {
		fiber_actual_sleep_ticks = test_fiber_sleep(ticks_to_sleep);
		fiber_sem_give(TEST_FIBER_SEM);
		nano_sem_take(&test_fiber_sem, TICKS_UNLIMITED);
	}
}

#define STACKSIZE 512
char __stack test_fiber_stack[STACKSIZE];

/**
 *
 * @brief Initialize test fiber data
 *
 * @return 0
 */
static int test_fiber_start(struct device *unused)
{
	ARG_UNUSED(unused);
	fiber_actual_sleep_ticks = 0;
	nano_sem_init(&test_fiber_sem);
	task_fiber_start(&test_fiber_stack[0], STACKSIZE,
					 (nano_fiber_entry_t) test_fiber,
					 FIBER_TICKS_TO_SLEEP, 0, 7, 0);
	return 0;
}

SYS_INIT(test_fiber_start, SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);


/**
 *
 * @brief Lower priority task to make sure that main task really sleeps
 *
 *
 *
 * @return N/A
 */
void AlternateTask(void)
{
	alternate_task_run = true;
}

/**
 *
 * @brief Regression task
 *
 * Checks the results of the early sleep
 *
 * @return N/A
 */
void RegressionTask(void)
{
	TC_START("Test early and regular task and fiber sleep functionality\n");
	alternate_task_run = false;

	TC_PRINT("Test fiber_sleep() call during the system initialization\n");
	/*
	 * Make sure that the fiber_sleep() called during the
	 * initialization has returned.
	 * fiber_sleep() invoked during the initialization for the
	 * shorter period that task_sleep() should return by now.
	 */
	if (task_sem_take(TEST_FIBER_SEM, TICKS_NONE) != RC_OK) {
		TC_ERROR("fiber_sleep() has not returned while expected\n");
	}

	/*
	 * Check that the fiber_sleep() called during the system
	 * initialization put the fiber to sleep for the specified
	 * amount of time
	 *
	 * On heavily loaded systems QEMU may demonstrate a drift
	 * of hardware clock ticks to system clock. Test verifies
	 * that sleep took at least not less amount of time.
	 * Allow up to 1 tick variance as the test may not have put
	 * the task to sleep on a tick boundary.
	 */
	if ((fiber_actual_sleep_ticks + 1) < FIBER_TICKS_TO_SLEEP) {
		TC_ERROR("fiber_sleep() time is too small: %d\n",
			fiber_actual_sleep_ticks);
		goto error_out;
	}

	/*
	 * Check that the task_sleep() called during the system
	 * initialization puts the task to sleep for the specified
	 * amount of time
	 */
	TC_PRINT("Test task_sleep() call during the system initialization\n");
	TC_PRINT("- At SECONDARY level\n");

	if ((task_actual_sleep_ticks + 1) < TASK_TICKS_TO_SLEEP) {
		TC_ERROR("task_sleep() time is is too small: %d\n",
			task_actual_sleep_ticks);
		goto error_out;
	}

	/*
	 * Check that the task_sleep() called during the system
	 * initialization at NANOKERNEL level puts the task to sleep for
	 * the specified amount of time
	 */
	TC_PRINT("- At NANOKERNEL level\n");

	if ((task_actual_sleep_nano_ticks + 1) < TASK_TICKS_TO_SLEEP) {
		TC_ERROR("task_sleep() time is is too small: %d\n",
			task_actual_sleep_nano_ticks);
		goto error_out;
	}

	/*
	 * Check that the task_sleep() called during the system
	 * initialization at MICROKERNEL level puts the task to sleep for
	 * the specified amount of time
	 */
	TC_PRINT("- At MICROKERNEL level\n");

	if ((task_actual_sleep_micro_ticks + 1) < TASK_TICKS_TO_SLEEP) {
		TC_ERROR("task_sleep() time is is too small: %d\n",
			task_actual_sleep_micro_ticks);
		goto error_out;
	}

	/*
	 * Check that the task_sleep() called during the system
	 * initialization at APPLICATION level puts the task to sleep for
	 * the specified amount of time
	 */
	TC_PRINT("- At APPLICATION level\n");

	if ((task_actual_sleep_app_ticks + 1) < TASK_TICKS_TO_SLEEP) {
		TC_ERROR("task_sleep() time is is too small: %d\n",
			task_actual_sleep_app_ticks);
		goto error_out;
	}

	/*
	 * Check that the task_sleep() called during the normal
	 * microkernel work put the task to sleep for the specified
	 * amount of time
	 */
	TC_PRINT("Test task_sleep() call on a running system\n");
	task_actual_sleep_ticks = test_task_sleep(TASK_TICKS_TO_SLEEP);

	if ((task_actual_sleep_ticks + 1) < TASK_TICKS_TO_SLEEP) {
		TC_ERROR("task_sleep() time is too small: %d\n",
			task_actual_sleep_ticks);
		goto error_out;
	}

	/* check that calling task_sleep() allowed the lower priority task run */
	if (!alternate_task_run) {
		TC_ERROR("Lower priority task did not run during task_sleep()\n");
		goto error_out;
	}

	/*
	 * Check that the fiber_sleep() called during the normal
	 * microkernel work put the fiber to sleep for the specified
	 * amount of time
	 */
	TC_PRINT("Test fiber_sleep() call on a running system\n");

	fiber_actual_sleep_ticks = 0;
	nano_sem_give(&test_fiber_sem);
	/* wait for the test fiber return from the sleep */
	task_sem_take(TEST_FIBER_SEM, TICKS_UNLIMITED);

	if ((fiber_actual_sleep_ticks + 1) < FIBER_TICKS_TO_SLEEP) {
		TC_ERROR("fiber_sleep() time is too small: %d\n",
			fiber_actual_sleep_ticks);
		goto error_out;
	}

	TC_END_RESULT(TC_PASS);
	TC_END_REPORT(TC_PASS);
	return;

error_out:
	TC_END_RESULT(TC_FAIL);
	TC_END_REPORT(TC_FAIL);
}