/*
 * Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * ARM64 Cortex-A ISRs wrapper
 */

#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <offsets_short.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/drivers/interrupt_controller/gic.h>
#include "macro_priv.inc"

_ASM_FILE_PROLOGUE

GDATA(_sw_isr_table)

/*
 * Wrapper around ISRs when inserted in software ISR table
 *
 * When inserted in the vector table, _isr_wrapper() demuxes the ISR table
 * using the running interrupt number as the index, and invokes the registered
 * ISR with its corresponding argument. When returning from the ISR, it
 * determines if a context switch needs to happen.
 */

GTEXT(_isr_wrapper)
SECTION_FUNC(TEXT, _isr_wrapper)

	/* ++_current_cpu->nested to be checked by arch_is_in_isr() */
	get_cpu	x0
	ldr	w1, [x0, #___cpu_t_nested_OFFSET]
	add	w2, w1, #1
	str	w2, [x0, #___cpu_t_nested_OFFSET]

	/* If not nested: switch to IRQ stack and save current sp on it. */
	cbnz	w1, 1f
	ldr	x1, [x0, #___cpu_t_irq_stack_OFFSET]
	mov	x2, sp
	mov	sp, x1
	str	x2, [sp, #-16]!
#if defined(CONFIG_ARM64_SAFE_EXCEPTION_STACK)
	sub	x1, x1, #CONFIG_ISR_STACK_SIZE
	str	x1, [x0, #_cpu_offset_to_current_stack_limit]
#endif
1:
#ifdef CONFIG_SCHED_THREAD_USAGE
	bl	z_sched_usage_stop
#endif

#ifdef CONFIG_TRACING
	bl	sys_trace_isr_enter
#endif

	/* Get active IRQ number from the interrupt controller */
#if !defined(CONFIG_ARM_CUSTOM_INTERRUPT_CONTROLLER)
	bl	arm_gic_get_active
#else
	bl	z_soc_irq_get_active
#endif /* !CONFIG_ARM_CUSTOM_INTERRUPT_CONTROLLER */

#if CONFIG_GIC_VER >= 3
	/*
	 * Ignore Special INTIDs 1020..1023 see 2.2.1 of Arm Generic Interrupt Controller
	 * Architecture Specification GIC architecture version 3 and version 4
	 */
	cmp	x0, 1019
	b.le	oob
	cmp	x0, 1023
	b.gt	oob
	b	spurious_continue

oob:
#endif
	/* IRQ out of bounds */
	mov	x1, #(CONFIG_NUM_IRQS - 1)
	cmp	x0, x1
	b.hi	spurious_continue

	stp	x0, xzr, [sp, #-16]!

	/* Retrieve the interrupt service routine */
	ldr	x1, =_sw_isr_table
	add	x1, x1, x0, lsl #4	/* table is 16-byte wide */
	ldp	x0, x3, [x1] /* arg in x0, ISR in x3 */

	/*
	 * Call the ISR. Unmask and mask again the IRQs to support nested
	 * exception handlers
	 */
	msr	daifclr, #(DAIFCLR_IRQ_BIT)
	blr	x3
	msr	daifset, #(DAIFSET_IRQ_BIT)

	/* Signal end-of-interrupt */
	ldp	x0, xzr, [sp], #16

spurious_continue:
#if !defined(CONFIG_ARM_CUSTOM_INTERRUPT_CONTROLLER)
	bl	arm_gic_eoi
#else
	bl	z_soc_irq_eoi
#endif /* !CONFIG_ARM_CUSTOM_INTERRUPT_CONTROLLER */

#ifdef CONFIG_TRACING
	bl	sys_trace_isr_exit
#endif

GTEXT(z_arm64_irq_done)
z_arm64_irq_done:
	/* if (--_current_cpu->nested != 0) exit */
	get_cpu	x0
	ldr	w1, [x0, #___cpu_t_nested_OFFSET]
	subs	w1, w1, #1
	str	w1, [x0, #___cpu_t_nested_OFFSET]
	bne	exit

	/* No more nested: retrieve the task's stack. */
	ldr	x1, [sp]
	mov	sp, x1

	/* retrieve pointer to the current thread */
	ldr	x1, [x0, #___cpu_t_current_OFFSET]

#if defined(CONFIG_ARM64_SAFE_EXCEPTION_STACK)
	/* arch_curr_cpu()->arch.current_stack_limit = thread->arch.stack_limit */
	ldr	x2, [x1, #_thread_offset_to_stack_limit]
	str	x2, [x0, #_cpu_offset_to_current_stack_limit]
#endif

	/*
	 * Get next thread to schedule with z_get_next_switch_handle().
	 * We pass it a NULL as we didn't save the whole thread context yet.
	 * If no scheduling is necessary then NULL will be returned.
	 */
	str	x1, [sp, #-16]!
	mov	x0, xzr
	bl	z_get_next_switch_handle
	ldr	x1, [sp], #16
	cbz	x0, exit

	/*
	 * Switch thread
	 * x0: new thread
	 * x1: old thread
	 */
	bl	z_arm64_context_switch

exit:
#ifdef CONFIG_STACK_SENTINEL
	bl	z_check_stack_sentinel
#endif
	b	z_arm64_exit_exc

