/* $NetBSD: interrupt.c,v 1.12 2023/10/06 11:45:16 skrll Exp $ */

/*-
 * Copyright (c) 1994, 1995, 1996 Carnegie-Mellon University.
 * All rights reserved.
 *
 * Authors: Keith Bostic, Chris G. Demetriou
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
 * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie the
 * rights to redistribute these changes.
 */
/*-
 * Additional Copyright (c) 1997 by Matthew Jacob for NASA/Ames Research Center.
 * Redistribute and modify at will, leaving only this additional copyright
 * notice.
 */

#include <sys/cdefs.h>			/* RCS ID & Copyright macro defns */
__KERNEL_RCSID(0, "$NetBSD: interrupt.c,v 1.12 2023/10/06 11:45:16 skrll Exp $");

#include "opt_ddb.h"

#include <sys/param.h>
#include <sys/evcnt.h>
#include <sys/lwp.h>
#include <sys/proc.h>
#include <sys/kmem.h>
#include <sys/sched.h>

#include <machine/clock.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/fpu.h>
#include <machine/frame.h>
#include <machine/intr.h>
#include <machine/md_var.h>
#include <machine/sapicvar.h>
#include <machine/smp.h>
#include <machine/userret.h>

#ifdef DDB
#include <ddb/ddb.h>
#endif


static void ia64_intr_eoi(void *);
static void ia64_intr_mask(void *);
#if 0
static void ia64_intr_unmask(void *);
#endif
static int ia64_dispatch_intr(void *, u_int);

#ifdef DDB
void db_print_vector(u_int, int);
#endif


int
interrupt(uint64_t vector, struct trapframe *tf)
{
	struct cpu_info *ci = curcpu();
	volatile struct ia64_interrupt_block *ib = IA64_INTERRUPT_BLOCK;
	uint64_t adj, clk, itc;
	int64_t delta;
	uint8_t inta;
	int count, handled = 0;

	ia64_set_fpsr(IA64_FPSR_DEFAULT);

	ci->ci_intrdepth++;
	ci->ci_data.cpu_nintr++;

 next:
	/*
	 * Handle ExtINT interrupts by generating an INTA cycle to
	 * read the vector.
	 */
	if (vector == 0) {
		inta = ib->ib_inta;
		printf("ExtINT interrupt: vector=%u\n", (int)inta);
		if (inta == 15) {
			__asm __volatile("mov cr.eoi = r0;; srlz.d");
			goto stray;
		}
		vector = (int)inta;
	} else if (vector == 15)
		goto stray;

	if (vector == CLOCK_VECTOR) {/* clock interrupt */
		itc = ia64_get_itc();

		adj = ci->ci_clockadj;
		clk = ci->ci_clock;
		delta = itc - clk;
		count = 0;
		while (delta >= ia64_clock_reload) {
			/* Only the BSP runs the real clock */
			if (ci->ci_cpuid == 0)
				hardclock((struct clockframe *)tf);
			else
				panic("CLOCK_VECTOR occur on not cpu0");
			delta -= ia64_clock_reload;
			clk += ia64_clock_reload;
			count++;
			handled = 1;
		}
		ia64_set_itm(ia64_get_itc() + ia64_clock_reload - adj);
		if (count > 0) {
			if (delta > (ia64_clock_reload >> 3)) {
				adj = ia64_clock_reload >> 4;
			} else
				adj = 0;
		} else
			adj = 0;
		ci->ci_clock = clk;
		ci->ci_clockadj = adj;
		ia64_srlz_d();

#ifdef MULTIPROCESSOR
	} else if (vector == ipi_vector[IPI_AST]) {
		asts[PCPU_GET(cpuid)]++;
		CTR1(KTR_SMP, "IPI_AST, cpuid=%d", PCPU_GET(cpuid));
	} else if (vector == ipi_vector[IPI_HIGH_FP]) {
		struct thread *thr = PCPU_GET(fpcurthread);

		if (thr != NULL) {
			mtx_lock_spin(&thr->td_md.md_highfp_mtx);
			save_high_fp(&thr->td_pcb->pcb_high_fp);
			thr->td_pcb->pcb_fpcpu = NULL;
			PCPU_SET(fpcurthread, NULL);
			mtx_unlock_spin(&thr->td_md.md_highfp_mtx);
		}
	} else if (vector == ipi_vector[IPI_RENDEZVOUS]) {
		rdvs[PCPU_GET(cpuid)]++;
		CTR1(KTR_SMP, "IPI_RENDEZVOUS, cpuid=%d", PCPU_GET(cpuid));
		enable_intr();
		smp_rendezvous_action();
		disable_intr();
	} else if (vector == ipi_vector[IPI_STOP]) {
		cpumask_t mybit = PCPU_GET(cpumask);

		savectx(PCPU_PTR(pcb));
		atomic_set_int(&stopped_cpus, mybit);
		while ((started_cpus & mybit) == 0)
			cpu_spinwait();
		atomic_clear_int(&started_cpus, mybit);
		atomic_clear_int(&stopped_cpus, mybit);
	} else if (vector == ipi_vector[IPI_PREEMPT]) {
		CTR1(KTR_SMP, "IPI_PREEMPT, cpuid=%d", PCPU_GET(cpuid));
		__asm __volatile("mov cr.eoi = r0;; srlz.d");
		enable_intr();
		sched_preempt(curthread);
		disable_intr();
		goto stray;
#endif
	} else {
		ci->ci_intrdepth++;
		handled = ia64_dispatch_intr(tf, vector);
		ci->ci_intrdepth--;
	}

	__asm __volatile("mov cr.eoi = r0;; srlz.d");
	vector = ia64_get_ivr();
	if (vector != 15)
		goto next;

stray:
	if (TRAPF_USERMODE(tf)) {
		enable_intr();
		userret(curlwp);
		do_ast(tf);
	}
	ci->ci_intrdepth--;
	return handled;
}


/*
 * Hardware irqs have vectors starting at this offset.
 */
#define IA64_HARDWARE_IRQ_BASE	0x20

struct ia64_intrhand {
	int (*ih_func)(void *);
	void *ih_arg;
	LIST_ENTRY(ia64_intrhand) ih_q;
	int ih_level;
	int ih_irq;
};
struct ia64_intr {
	u_int irq;
	struct sapic *sapic;
	int type;

	LIST_HEAD(, ia64_intrhand) intr_q;

	char evname[32];
	struct evcnt evcnt;
};

static struct ia64_intr *ia64_intrs[256];


static void
ia64_intr_eoi(void *arg)
{
	u_int vector = (uintptr_t)arg;
	struct ia64_intr *i;

	i = ia64_intrs[vector];
	if (i != NULL)
		sapic_eoi(i->sapic, vector);
}

static void
ia64_intr_mask(void *arg)
{
	u_int vector = (uintptr_t)arg;
	struct ia64_intr *i;

	i = ia64_intrs[vector];
	if (i != NULL) {
		sapic_mask(i->sapic, i->irq);
		sapic_eoi(i->sapic, vector);
	}
}

#if 0
static void
ia64_intr_unmask(void *arg)
{
	u_int vector = (uintptr_t)arg;
	struct ia64_intr *i;

	i = ia64_intrs[vector];
	if (i != NULL)
		sapic_unmask(i->sapic, i->irq);
}
#endif

void *
intr_establish_xname(int irq, int type, int level, int (*func)(void *),
    void *arg, const char *xname)
{
	/* TODO: xname support */
	return intr_establish(irq, type, level, func, arg);
}

void *
intr_establish(int irq, int type, int level, int (*func)(void *), void *arg)
{
	struct ia64_intr *i;
	struct ia64_intrhand *ih;
	struct sapic *sa;
	u_int vector;

	/* Get the I/O SAPIC that corresponds to the IRQ. */
	sa = sapic_lookup(irq);
	if (sa == NULL)
		return NULL;

	switch (type) {
	case IST_EDGE:
	case IST_LEVEL:
		break;

	default:
		return NULL;
	}

	/*
	 * XXX - There's a priority implied by the choice of vector.
	 * We should therefore relate the vector to the interrupt type.
	 */
	vector = irq + IA64_HARDWARE_IRQ_BASE;

	i = ia64_intrs[vector];
	if (i == NULL) {
		i = kmem_alloc(sizeof(struct ia64_intr), KM_SLEEP);
		i->irq = irq;
		i->sapic = sa;
		i->type = type;
		LIST_INIT(&i->intr_q);
		snprintf(i->evname, sizeof(i->evname), "irq %d", irq);
		evcnt_attach_dynamic(&i->evcnt, EVCNT_TYPE_INTR, NULL,
		    "iosapic", i->evname);
		ia64_intrs[vector] = i;

		sapic_config_intr(irq, type);
		sapic_enable(i->sapic, irq, vector);
	} else
		if (i->type != type)
			return NULL;

	ih = kmem_alloc(sizeof(*ih), KM_SLEEP);
	ih->ih_func = func;
	ih->ih_arg = arg;
	ih->ih_level = level;
	ih->ih_irq = irq;
	LIST_INSERT_HEAD(&i->intr_q, ih, ih_q);

	return ih;
}

void
intr_disestablish(void *cookie)
{
	struct ia64_intr *i;
	struct ia64_intrhand *ih = cookie;
	u_int vector = ih->ih_irq + IA64_HARDWARE_IRQ_BASE;

	i = ia64_intrs[vector];

	LIST_REMOVE(ih, ih_q);
	if (LIST_FIRST(&i->intr_q) == NULL) {
		ia64_intr_mask((void *)(uintptr_t)vector);

		ia64_intrs[vector] = NULL;
		evcnt_detach(&i->evcnt);
		kmem_free(i, sizeof(*i));
	}

	kmem_free(ih, sizeof(*ih));
}

static int
ia64_dispatch_intr(void *frame, u_int vector)
{
	struct ia64_intr *i;
	struct ia64_intrhand *ih;
	int handled = 0;

	/*
	 * Find the interrupt thread for this vector.
	 */
	i = ia64_intrs[vector];
	KASSERT(i != NULL);

	i->evcnt.ev_count++;

	LIST_FOREACH(ih, &i->intr_q, ih_q) {
		if (__predict_false(ih->ih_func == NULL))
			printf("%s: spurious interrupt (irq = %d)\n",
			    __func__, ih->ih_irq);
		else if (__predict_true((*ih->ih_func)(ih->ih_arg)))
			handled = 1;
	}
	ia64_intr_eoi((void *)(uintptr_t)vector);

	return handled;
}

void
ia64_handle_intr(void *tf)
{
	panic("XXX %s not implemented", __func__);
}

#ifdef DDB
void
db_print_vector(u_int vector, int always)
{
	struct ia64_intr *i;

	i = ia64_intrs[vector];
	if (i != NULL) {
		db_printf("vector %u (%p): ", vector, i);
		sapic_print(i->sapic, i->irq);
	} else if (always)
		db_printf("vector %u: unassigned\n", vector);
}

const char *
intr_string(intr_handle_t ih, char *buf, size_t len)
{
	panic("XXX %s not implemented", __func__);
}
#endif
