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

/**
 * @file
 *
 * This test case verifies the correctness of irq_offload(), an important
 * routine used in many other test cases for running a function in interrupt
 * context, on the IRQ stack.
 *
 */
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/irq_offload.h>

volatile uint32_t sentinel;
#define SENTINEL_VALUE 0xDEADBEEF

K_THREAD_STACK_DEFINE(offload_stack, 384 + CONFIG_TEST_EXTRA_STACK_SIZE);
struct k_thread offload_thread;

static void offload_function(const void *param)
{
	uint32_t x = POINTER_TO_INT(param);

	/* Make sure we're in IRQ context */
	zassert_true(k_is_in_isr(), "Not in IRQ context!");

	sentinel = x;
}

/**
 * @brief Verify thread context
 *
 * @ingroup kernel_interrupt_tests
 *
 * @details Check whether offloaded running function is in interrupt
 * context, on the IRQ stack or not.
 */
ZTEST(irq_offload, test_irq_offload)
{
	/* Simple validation of nested locking. */
	unsigned int key1, key2;

	key1 = arch_irq_lock();
	zassert_true(arch_irq_unlocked(key1),
		     "IRQs should have been unlocked, but key is 0x%x\n",
		     key1);
	key2 = arch_irq_lock();
	zassert_false(arch_irq_unlocked(key2),
		      "IRQs should have been locked, but key is 0x%x\n",
		      key2);
	arch_irq_unlock(key2);
	arch_irq_unlock(key1);

	/**TESTPOINT: Offload to IRQ context*/
	irq_offload(offload_function, (const void *)SENTINEL_VALUE);

	zassert_equal(sentinel, SENTINEL_VALUE,
		      "irq_offload() didn't work properly");
}

static struct k_timer nestoff_timer;
static bool timer_executed, nested_executed;

void nestoff_offload(const void *parameter)
{
	/* Suspend the thread we interrupted so we context switch, see below */
	k_thread_suspend(&offload_thread);

	nested_executed = true;
}


static void nestoff_timer_fn(struct k_timer *timer)
{
	zassert_false(nested_executed, "nested irq_offload ran too soon");
	irq_offload(nestoff_offload, NULL);
	zassert_true(nested_executed, "nested irq_offload did not run");

	/* Set this last, to be sure we return to this context and not
	 * the enclosing interrupt
	 */
	timer_executed = true;
}

static void offload_thread_fn(void *p0, void *p1, void *p2)
{
	k_timer_start(&nestoff_timer, K_TICKS(1), K_FOREVER);

	while (true) {
		zassert_false(timer_executed, "should not return to this thread");
	}
}

/* Invoke irq_offload() from an interrupt and verify that the
 * resulting nested interrupt doesn't explode
 */
ZTEST(common_1cpu, test_nested_irq_offload)
{
	if (!IS_ENABLED(CONFIG_IRQ_OFFLOAD_NESTED)) {
		ztest_test_skip();
	}

	k_thread_priority_set(k_current_get(), 1);

	k_timer_init(&nestoff_timer, nestoff_timer_fn, NULL);

	zassert_false(timer_executed, "timer ran too soon");
	zassert_false(nested_executed, "nested irq_offload ran too soon");

	/* Do this in a thread to exercise a regression case: the
	 * offload handler will suspend the thread it interrupted,
	 * ensuring that the interrupt returns back to this thread and
	 * effects a context switch of the nested interrupt (see
	 * #45779).  Requires that this be a 1cpu test case,
	 * obviously.
	 */
	k_thread_create(&offload_thread,
			offload_stack, K_THREAD_STACK_SIZEOF(offload_stack),
			offload_thread_fn, NULL, NULL, NULL,
			0, 0, K_NO_WAIT);

	zassert_true(timer_executed, "timer did not run");
	zassert_true(nested_executed, "nested irq_offload did not run");

	k_thread_abort(&offload_thread);
}

extern void *common_setup(void);
ZTEST_SUITE(irq_offload, NULL, common_setup, NULL, NULL, NULL);
