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

/**
 * @file
 * @brief Interrupt support for IA-32 arch
 *
 * INTERNAL
 * The _idt_base_address symbol is used to determine the base address of the IDT.
 * (It is generated by the linker script, and doesn't correspond to an actual
 * global variable.)
 */

#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/irq.h>
#include <zephyr/tracing/tracing.h>
#include <kswap.h>
#include <zephyr/arch/x86/ia32/segmentation.h>

extern void z_SpuriousIntHandler(void *handler);
extern void z_SpuriousIntNoErrCodeHandler(void *handler);

/*
 * Place the addresses of the spurious interrupt handlers into the intList
 * section. The genIdt tool can then populate any unused vectors with
 * these routines.
 */
void *__attribute__((section(".spurIsr"))) MK_ISR_NAME(z_SpuriousIntHandler) =
	&z_SpuriousIntHandler;
void *__attribute__((section(".spurNoErrIsr")))
	MK_ISR_NAME(z_SpuriousIntNoErrCodeHandler) =
		&z_SpuriousIntNoErrCodeHandler;

__pinned_func
void arch_isr_direct_footer_swap(unsigned int key)
{
	(void)z_swap_irqlock(key);
}

#if CONFIG_X86_DYNAMIC_IRQ_STUBS > 0

/*
 * z_interrupt_vectors_allocated[] bitfield is generated by the 'gen_idt' tool.
 * It is initialized to identify which interrupts have been statically
 * connected and which interrupts are available to be dynamically connected at
 * run time, with a 1 bit indicating a free vector.  The variable itself is
 * defined in the linker file.
 */
extern unsigned int z_interrupt_vectors_allocated[];

struct dyn_irq_info {
	/** IRQ handler */
	void (*handler)(const void *param);
	/** Parameter to pass to the handler */
	const void *param;
};

/*
 * Instead of creating a large sparse table mapping all possible IDT vectors
 * to dyn_irq_info, the dynamic stubs push a "stub id" onto the stack
 * which is used by common_dynamic_handler() to fetch the appropriate
 * information out of this much smaller table
 */
__pinned_bss
static struct dyn_irq_info dyn_irq_list[CONFIG_X86_DYNAMIC_IRQ_STUBS];

__pinned_bss
static unsigned int next_irq_stub;

/* Memory address pointing to where in ROM the code for the dynamic stubs are.
 * Linker symbol.
 */
extern char z_dynamic_stubs_begin[];

/**
 * @brief Allocate a free interrupt vector given <priority>
 *
 * This routine scans the z_interrupt_vectors_allocated[] array for a free vector
 * that satisfies the specified <priority>.
 *
 * This routine assumes that the relationship between interrupt priority and
 * interrupt vector is :
 *
 *      priority = (vector / 16) - 2;
 *
 * Vectors 0 to 31 are reserved for CPU exceptions and do NOT fall under
 * the priority scheme. The first vector used for priority level 0 will be 32.
 * Each interrupt priority level contains 16 vectors.
 *
 * It is also assumed that the interrupt controllers are capable of managing
 * interrupt requests on a per-vector level as opposed to a per-priority level.
 * For example, the local APIC on Pentium4 and later processors, the in-service
 * register (ISR) and the interrupt request register (IRR) are 256 bits wide.
 *
 * @return allocated interrupt vector
 */

static unsigned int priority_to_free_vector(unsigned int requested_priority)
{
	unsigned int entry;
	unsigned int fsb; /* first set bit in entry */
	unsigned int search_set;
	unsigned int vector_block;
	unsigned int vector;

	static unsigned int mask[2] = {0x0000ffffU, 0xffff0000U};

	vector_block = requested_priority + 2;

	__ASSERT(((vector_block << 4) + 15) <= CONFIG_IDT_NUM_VECTORS,
		 "IDT too small (%d entries) to use priority %d",
		 CONFIG_IDT_NUM_VECTORS, requested_priority);

	/*
	 * Atomically allocate a vector from the
	 * z_interrupt_vectors_allocated[] array to prevent race conditions
	 * with other threads attempting to allocate an interrupt
	 * vector.
	 *
	 * Note: As z_interrupt_vectors_allocated[] is initialized by the
	 * 'gen_idt.py' tool, it is critical that this routine use the same
	 * algorithm as the 'gen_idt.py' tool for allocating interrupt vectors.
	 */

	entry = vector_block >> 1;

	/*
	 * The z_interrupt_vectors_allocated[] entry indexed by 'entry'
	 * is a 32-bit quantity and thus represents the vectors for a pair of
	 * priority levels. Mask out the unwanted priority level and then use
	 * find_lsb_set() to scan for an available vector of the requested
	 * priority.
	 *
	 * Note that find_lsb_set() returns bit position from 1 to 32, or 0 if
	 * the argument is zero.
	 */
	search_set = mask[vector_block & 1] &
			z_interrupt_vectors_allocated[entry];
	fsb = find_lsb_set(search_set);

	__ASSERT(fsb != 0U, "No remaning vectors for priority level %d",
		 requested_priority);

	/*
	 * An available vector of the requested priority was found.
	 * Mark it as allocated by clearing the bit.
	 */
	--fsb;
	z_interrupt_vectors_allocated[entry] &= ~BIT(fsb);

	/* compute vector given allocated bit within the priority level */
	vector = (entry << 5) + fsb;

	return vector;
}

/**
 * @brief Get the memory address of an unused dynamic IRQ or exception stub
 *
 * We generate at build time a set of dynamic stubs which push
 * a stub index onto the stack for use as an argument by
 * common handling code.
 *
 * @param stub_idx Stub number to fetch the corresponding stub function
 * @return Pointer to the stub code to install into the IDT
 */
__pinned_func
static void *get_dynamic_stub(int stub_idx)
{
	uint32_t offset;

	/*
	 * Because we want the sizes of the stubs to be consistent and minimized,
	 * stubs are grouped into blocks, each containing a push and subsequent
	 * 2-byte jump instruction to the end of the block, which then contains
	 * a larger jump instruction to common dynamic IRQ handling code
	 */
	offset = (stub_idx * Z_DYN_STUB_SIZE) +
		((stub_idx / Z_DYN_STUB_PER_BLOCK) *
		 Z_DYN_STUB_LONG_JMP_EXTRA_SIZE);

	return (void *)((uint32_t)&z_dynamic_stubs_begin + offset);
}

extern const struct pseudo_descriptor z_x86_idt;

static void idt_vector_install(int vector, void *irq_handler)
{
	unsigned int key;

	key = irq_lock();
	z_init_irq_gate(&z_x86_idt.entries[vector], CODE_SEG,
		       (uint32_t)irq_handler, 0);
	irq_unlock(key);
}

int arch_irq_connect_dynamic(unsigned int irq, unsigned int priority,
			     void (*routine)(const void *parameter),
			     const void *parameter, uint32_t flags)
{
	int vector, stub_idx, key;

	key = irq_lock();

	vector = priority_to_free_vector(priority);
	/* 0 indicates not used, vectors for interrupts start at 32 */
	__ASSERT(_irq_to_interrupt_vector[irq] == 0U,
		 "IRQ %d already configured", irq);
	_irq_to_interrupt_vector[irq] = vector;
	z_irq_controller_irq_config(vector, irq, flags);

	stub_idx = next_irq_stub++;
	__ASSERT(stub_idx < CONFIG_X86_DYNAMIC_IRQ_STUBS,
		 "No available interrupt stubs found");

	dyn_irq_list[stub_idx].handler = routine;
	dyn_irq_list[stub_idx].param = parameter;
	idt_vector_install(vector, get_dynamic_stub(stub_idx));

	irq_unlock(key);

	return vector;
}

/**
 * @brief Common dynamic IRQ handler function
 *
 * This gets called by the IRQ entry asm code with the stub index supplied as
 * an argument. Look up the required information in dyn_irq_list and
 * execute it.
 *
 * @param stub_idx Index into the dyn_irq_list array
 */
__pinned_func
void z_x86_dynamic_irq_handler(uint8_t stub_idx)
{
	dyn_irq_list[stub_idx].handler(dyn_irq_list[stub_idx].param);
}
#endif /* CONFIG_X86_DYNAMIC_IRQ_STUBS > 0 */
