/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <kernel.h>
#include <stdbool.h>
#include <ztest.h>

#include "ec_tasks.h"
#include "task.h"
#include "timer.h"

/* Second for platform/ec task API (in microseconds). */
#define TASK_SEC(s) (s * 1000 * 1000)

K_SEM_DEFINE(task_done1, 0, 1);
K_SEM_DEFINE(task_done2, 0, 1);
K_SEM_DEFINE(test_ready1, 0, 1);
K_SEM_DEFINE(test_ready2, 0, 1);

static void (*task1)(void);
static void (*task2)(void);

static void run_test(void (*task1_run)(void), void (*task2_run)(void))
{
	task1 = task1_run;
	task2 = task2_run;
	k_sem_give(&test_ready1);
	k_sem_give(&test_ready2);
	k_sem_take(&task_done1, K_FOREVER);
	k_sem_take(&task_done2, K_FOREVER);
}

void task1_entry(void *p)
{
	while (1) {
		k_sem_take(&test_ready1, K_FOREVER);
		task1();
		k_sem_give(&task_done1);
	}
}

void task2_entry(void *p)
{
	while (1) {
		k_sem_take(&test_ready2, K_FOREVER);
		task2();
		k_sem_give(&task_done2);
	}
}

/*
 * Unlike Tasks 1 & 2, it is allowed to run Task 3 more than once per
 * call to run_test().  It will call task3_entry_func if set, and wait
 * for the next event.  This is useful to test things like timers,
 * which you are expecting the event to fire at some point in the
 * future, and you want to test that it happens.
 */
static void (*task3_entry_func)(uint32_t event_mask);

void task3_entry(void *p)
{
	uint32_t events = 0;

	for (;;) {
		if (task3_entry_func)
			task3_entry_func(events);
		events = task_wait_event(-1);
	}
}

static void set_event_before_task_start1(void)
{
	const uint32_t events = task_wait_event(TASK_SEC(2));

	zassert_equal(events, 0xAAAA, "Should have 0xAAAA events");
}

static void set_event_before_task_start2(void)
{
	/* Do nothing */
}

static void test_set_event_before_task_start(void)
{
	/* Send event before tasks start */
	task_set_event(TASK_ID_TASK_1, 0xAAAA);

	start_ec_tasks();

	run_test(set_event_before_task_start1, set_event_before_task_start2);
}

static void task_get_current1(void)
{
	zassert_equal(task_get_current(), TASK_ID_TASK_1, "ID matches");
}

static void task_get_current2(void)
{
	zassert_equal(task_get_current(), TASK_ID_TASK_2, "ID matches");
}

static void test_task_get_current(void)
{
	run_test(&task_get_current1, &task_get_current2);
}


static void timeout1(void)
{
	const uint32_t start_ms = k_uptime_get();
	const uint32_t events = task_wait_event(TASK_SEC(2));
	const uint32_t end_ms = k_uptime_get();

	zassert_equal(events, TASK_EVENT_TIMER, "Should have timeout event");
	zassert_within(end_ms - start_ms, 2000, 100, "Timeout for 2 seconds");
}

static void timeout2(void)
{
	/* Do nothing */
}

static void test_timeout(void)
{
	run_test(&timeout1, &timeout2);
}

/*
 * Timer test:
 *   1. Task 1 arms a timer for Task 3 in expiring 2 seconds.
 *   2. Task 2 does nothing.
 *   3. Task 3 validates that the it receives a TASK_EVENT_TIMER event
 *      2 seconds after Task 1 armed the timer (within 100ms
 *      tolerance).
 */
static timestamp_t timer_armed_at;
K_SEM_DEFINE(check_timer_finished, 0, 1);

static void check_timer(uint32_t event_mask)
{
	timestamp_t now = get_time();

	zassert_equal(event_mask & TASK_EVENT_TIMER, TASK_EVENT_TIMER,
		      "Timer event mask should be set");
	zassert_within(now.val - timer_armed_at.val, TASK_SEC(2),
		       TASK_SEC(1) / 10,
		       "Timer should expire at 2 seconds from arm time");
	k_sem_give(&check_timer_finished);
}

static void timer_task_1(void)
{
	timestamp_t timer_timeout;

	timer_armed_at = get_time();

	timer_timeout.val = timer_armed_at.val + TASK_SEC(2);

	task3_entry_func = check_timer;
	zassert_equal(timer_arm(timer_timeout, TASK_ID_TASK_3), EC_SUCCESS,
		      "Setting timer should succeed");
}

static void timer_task_2(void)
{
	/* Do nothing */
}

static void test_timer(void)
{
	run_test(timer_task_1, timer_task_2);
	zassert_equal(k_sem_take(&check_timer_finished, K_SECONDS(4 * 1000)), 0,
		      "Task 3 did not finish within timeout");
	zassert_equal(task3_entry_func, check_timer,
		      "check_timer should have been enabled");
	task3_entry_func = NULL;
}

static void event_delivered1(void)
{
	const uint32_t start_ms = k_uptime_get();
	const uint32_t events = task_wait_event(-1);
	const uint32_t end_ms = k_uptime_get();

	zassert_equal(events, 0x1234, "Verify event bits");
	zassert_within(end_ms - start_ms, 5000, 100, "Waited for 5 seconds");
}

static void event_delivered2(void)
{
	k_sleep(K_SECONDS(5));

	task_set_event(TASK_ID_TASK_1, 0x1234);
}

static void test_event_delivered(void)
{
	run_test(&event_delivered1, &event_delivered2);
}


static void event_mask_not_delivered1(void)
{
	task_set_event(TASK_ID_TASK_2, 0x007F);
}

static void event_mask_not_delivered2(void)
{
	const uint32_t start_ms = k_uptime_get();
	const uint32_t events = task_wait_event_mask(0x0080, TASK_SEC(7));
	const uint32_t end_ms = k_uptime_get();

	zassert_equal(events, TASK_EVENT_TIMER, "Should have timeout event");
	zassert_within(end_ms - start_ms, 7000, 100, "Timeout for 7 seconds");

	const uint32_t leftover_events = task_wait_event(0);

	zassert_equal(leftover_events, 0x007F, "All events should be waiting");
}

static void test_event_mask_not_delivered(void)
{
	run_test(&event_mask_not_delivered1, &event_mask_not_delivered2);
}


static void event_mask_extra1(void)
{
	k_sleep(K_SECONDS(1));

	task_set_event(TASK_ID_TASK_2, 0x00FF);
}

static void event_mask_extra2(void)
{
	const uint32_t start_ms = k_uptime_get();
	const uint32_t events = task_wait_event_mask(0x0001, TASK_SEC(10));
	const uint32_t end_ms = k_uptime_get();

	zassert_equal(events, 0x0001, "Verify only waited for event");
	zassert_within(end_ms - start_ms, 1000, 100, "Timeout for 1 second");

	const uint32_t leftover_events = task_wait_event(0);

	zassert_equal(leftover_events, 0x00FE, "All events should be waiting");
}

static void test_event_mask_extra(void)
{
	run_test(&event_mask_extra1, &event_mask_extra2);
}


static void empty_set_mask1(void)
{
	k_sleep(K_SECONDS(1));
	/*
	 * It is generally invalid to set a 0 event, but this simulates a race
	 * condition and exercises fallback code in task_wait_event
	 */
	task_set_event(TASK_ID_TASK_2, 0);
	k_sleep(K_SECONDS(1));
	task_set_event(TASK_ID_TASK_2, 0x1234);
}

static void empty_set_mask2(void)
{
	const uint32_t start_ms = k_uptime_get();
	const uint32_t events = task_wait_event_mask(0x1234, TASK_SEC(10));
	const uint32_t end_ms = k_uptime_get();

	zassert_equal(events, 0x1234, "Verify only waited for event");
	zassert_within(end_ms - start_ms, 2000, 100, "Timeout for 2 seconds");
}

static void test_empty_set_mask(void)
{
	run_test(&empty_set_mask1, &empty_set_mask2);
}


void test_main(void)
{
	/* Note that test_set_event_before_task_start calls start_ec_tasks */
	ztest_test_suite(test_task_shim,
			 ztest_unit_test(test_set_event_before_task_start),
			 ztest_unit_test(test_task_get_current),
			 ztest_unit_test(test_timeout),
			 ztest_unit_test(test_timer),
			 ztest_unit_test(test_event_delivered),
			 ztest_unit_test(test_event_mask_not_delivered),
			 ztest_unit_test(test_event_mask_extra),
			 ztest_unit_test(test_empty_set_mask));
	ztest_run_test_suite(test_task_shim);
}
