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

#include <zephyr/sw_isr_table.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/irq.h>
#include <zephyr/sys/__assert.h>
/*
 * Common code for arches that use software ISR tables (CONFIG_GEN_ISR_TABLES)
 */

#ifdef CONFIG_DYNAMIC_INTERRUPTS

#ifdef CONFIG_MULTI_LEVEL_INTERRUPTS

struct irq_parent_offset {
	unsigned int irq;
	unsigned int offset;
};

#define INIT_IRQ_PARENT_OFFSET(i, o) { \
	.irq = i, \
	.offset = o, \
}

#define IRQ_INDEX_TO_OFFSET(i, base) (base + i * CONFIG_MAX_IRQ_PER_AGGREGATOR)

#ifdef CONFIG_2ND_LEVEL_INTERRUPTS

#define CAT_2ND_LVL_LIST(i, base) \
	INIT_IRQ_PARENT_OFFSET(CONFIG_2ND_LVL_INTR_0##i##_OFFSET, \
		IRQ_INDEX_TO_OFFSET(i, base))
static struct irq_parent_offset lvl2_irq_list[CONFIG_NUM_2ND_LEVEL_AGGREGATORS]
	= { LISTIFY(CONFIG_NUM_2ND_LEVEL_AGGREGATORS, CAT_2ND_LVL_LIST, (,),
		CONFIG_2ND_LVL_ISR_TBL_OFFSET) };

#endif/* CONFIG_2ND_LEVEL_INTERRUPTS */

#ifdef CONFIG_3RD_LEVEL_INTERRUPTS

#define CAT_3RD_LVL_LIST(i, base) \
	INIT_IRQ_PARENT_OFFSET(CONFIG_3RD_LVL_INTR_0##i##_OFFSET, \
		IRQ_INDEX_TO_OFFSET(i, base))
static struct irq_parent_offset lvl3_irq_list[CONFIG_NUM_3RD_LEVEL_AGGREGATORS]
	 = { LISTIFY(CONFIG_NUM_3RD_LEVEL_AGGREGATORS, CAT_3RD_LVL_LIST, (,),
		CONFIG_3RD_LVL_ISR_TBL_OFFSET) };

#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */

unsigned int get_parent_offset(unsigned int parent_irq,
					struct irq_parent_offset list[],
					unsigned int length)
{
	unsigned int i;
	unsigned int offset = 0U;

	for (i = 0U; i < length; ++i) {
		if (list[i].irq == parent_irq) {
			offset = list[i].offset;
			break;
		}
	}

	__ASSERT(i != length, "Invalid argument: %i", parent_irq);

	return offset;
}

#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */

unsigned int z_get_sw_isr_table_idx(unsigned int irq)
{
	unsigned int table_idx;

#ifdef CONFIG_MULTI_LEVEL_INTERRUPTS
	unsigned int level, parent_irq, parent_offset;

	level = irq_get_level(irq);

	if (level == 2U) {
		parent_irq = irq_parent_level_2(irq);
		parent_offset = get_parent_offset(parent_irq,
						  lvl2_irq_list,
						  CONFIG_NUM_2ND_LEVEL_AGGREGATORS);
		table_idx = parent_offset + irq_from_level_2(irq);
	}
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
	else if (level == 3U) {
		parent_irq = irq_parent_level_3(irq);
		parent_offset = get_parent_offset(parent_irq,
						  lvl3_irq_list,
						  CONFIG_NUM_3RD_LEVEL_AGGREGATORS);
		table_idx = parent_offset + irq_from_level_3(irq);
	}
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */
	else {
		table_idx = irq;
	}

	table_idx -= CONFIG_GEN_IRQ_START_VECTOR;
#else
	table_idx = irq - CONFIG_GEN_IRQ_START_VECTOR;
#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */

	return table_idx;
}

void __weak z_isr_install(unsigned int irq, void (*routine)(const void *),
			  const void *param)
{
	unsigned int table_idx;

	/*
	 * Do not assert on the IRQ enable status for ARM GIC since the SGI
	 * type interrupts are always enabled and attempting to install an ISR
	 * for them will cause the assertion to fail.
	 */
#ifndef CONFIG_GIC
	__ASSERT(!irq_is_enabled(irq), "IRQ %d is enabled", irq);
#endif /* !CONFIG_GIC */

	table_idx = z_get_sw_isr_table_idx(irq);

	/* If dynamic IRQs are enabled, then the _sw_isr_table is in RAM and
	 * can be modified
	 */
	_sw_isr_table[table_idx].arg = param;
	_sw_isr_table[table_idx].isr = routine;
}

/* Some architectures don't/can't interpret flags or priority and have
 * no more processing to do than this.  Provide a generic fallback.
 */
int __weak arch_irq_connect_dynamic(unsigned int irq,
				    unsigned int priority,
				    void (*routine)(const void *),
				    const void *parameter,
				    uint32_t flags)
{
	ARG_UNUSED(flags);
	ARG_UNUSED(priority);

	z_isr_install(irq, routine, parameter);
	return irq;
}

#endif /* CONFIG_DYNAMIC_INTERRUPTS */
