/*-
 * Copyright (c) 2010-2011 Marcel Moolenaar
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "opt_ddb.h"

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.1.0/sys/ia64/ia64/interrupt.c 224114 2011-07-16 20:16:49Z marcel $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/vmmeter.h>
#include <sys/bus.h>
#include <sys/interrupt.h>
#include <sys/malloc.h>
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>

#include <machine/cpu.h>
#include <machine/fpu.h>
#include <machine/frame.h>
#include <machine/intr.h>
#include <machine/intrcnt.h>
#include <machine/md_var.h>
#include <machine/pcb.h>
#include <machine/reg.h>
#include <machine/smp.h>

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

struct ia64_intr {
	struct intr_event *event;	/* interrupt event */
	volatile long *cntp;		/* interrupt counter */
	struct sapic *sapic;
	u_int	irq;
};

ia64_ihtype *ia64_handler[IA64_NXIVS];

static enum ia64_xiv_use ia64_xiv[IA64_NXIVS];
static struct ia64_intr *ia64_intrs[IA64_NXIVS];

static ia64_ihtype ia64_ih_invalid;
static ia64_ihtype ia64_ih_irq;

void
ia64_xiv_init(void)
{
	u_int xiv;

	for (xiv = 0; xiv < IA64_NXIVS; xiv++) {
		ia64_handler[xiv] = ia64_ih_invalid;
		ia64_xiv[xiv] = IA64_XIV_FREE;
		ia64_intrs[xiv] = NULL;
	}
	(void)ia64_xiv_reserve(15, IA64_XIV_ARCH, NULL);
}

int
ia64_xiv_free(u_int xiv, enum ia64_xiv_use what)
{

	if (xiv >= IA64_NXIVS)
		return (EINVAL);
	if (what == IA64_XIV_FREE || what == IA64_XIV_ARCH)
		return (EINVAL);
	if (ia64_xiv[xiv] != what)
		return (ENXIO);
	ia64_xiv[xiv] = IA64_XIV_FREE;
	ia64_handler[xiv] = ia64_ih_invalid;
	return (0);
}

int
ia64_xiv_reserve(u_int xiv, enum ia64_xiv_use what, ia64_ihtype ih)
{

	if (xiv >= IA64_NXIVS)
		return (EINVAL);
	if (what == IA64_XIV_FREE)
		return (EINVAL);
	if (ia64_xiv[xiv] != IA64_XIV_FREE)
		return (EBUSY);
	ia64_xiv[xiv] = what;
	ia64_handler[xiv] = (ih == NULL) ? ia64_ih_invalid: ih;
	if (bootverbose)
		printf("XIV %u: use=%u, IH=%p\n", xiv, what, ih);
	return (0);
}

u_int
ia64_xiv_alloc(u_int prio, enum ia64_xiv_use what, ia64_ihtype ih)
{
	u_int hwprio;
	u_int xiv0, xiv;

	hwprio = prio >> 2;
	if (hwprio > IA64_MAX_HWPRIO)
		hwprio = IA64_MAX_HWPRIO;

	xiv0 = IA64_NXIVS - (hwprio + 1) * 16;

	KASSERT(xiv0 >= IA64_MIN_XIV, ("%s: min XIV", __func__));
	KASSERT(xiv0 < IA64_NXIVS, ("%s: max XIV", __func__));

	xiv = xiv0;
	while (xiv < IA64_NXIVS && ia64_xiv_reserve(xiv, what, ih))
		xiv++;

	if (xiv < IA64_NXIVS)
		return (xiv);

	xiv = xiv0;
	while (xiv >= IA64_MIN_XIV && ia64_xiv_reserve(xiv, what, ih))
		xiv--;

	return ((xiv >= IA64_MIN_XIV) ? xiv : 0);
}

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

	i = ia64_intrs[xiv];
	KASSERT(i != NULL, ("%s", __func__));
	sapic_eoi(i->sapic, xiv);
}

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

	i = ia64_intrs[xiv];
	KASSERT(i != NULL, ("%s", __func__));
	sapic_mask(i->sapic, i->irq);
	sapic_eoi(i->sapic, xiv);
}

static void
ia64_intr_unmask(void *arg)
{
	u_int xiv = (uintptr_t)arg;
	struct ia64_intr *i;

	i = ia64_intrs[xiv];
	KASSERT(i != NULL, ("%s", __func__));
	sapic_unmask(i->sapic, i->irq);
}

int
ia64_setup_intr(const char *name, int irq, driver_filter_t filter,
    driver_intr_t handler, void *arg, enum intr_type flags, void **cookiep)
{
	struct ia64_intr *i;
	struct sapic *sa;
	char *intrname;
	u_int prio, xiv;
	int error;

	prio = intr_priority(flags);
	if (prio > PRI_MAX_ITHD)
		return (EINVAL);

	/* XXX lock */

	/* Get the I/O SAPIC and XIV that corresponds to the IRQ. */
	sa = sapic_lookup(irq, &xiv);
	if (sa == NULL) {
		/* XXX unlock */
		return (EINVAL);
	}

	if (xiv == 0) {
		/* XXX unlock */
		i = malloc(sizeof(struct ia64_intr), M_DEVBUF,
		    M_ZERO | M_WAITOK);
		/* XXX lock */
		sa = sapic_lookup(irq, &xiv);
		KASSERT(sa != NULL, ("sapic_lookup"));
		if (xiv != 0)
			free(i, M_DEVBUF);
	}

	/*
	 * If the IRQ has no XIV assigned to it yet, assign one based
	 * on the priority.
	 */
	if (xiv == 0) {
		xiv = ia64_xiv_alloc(prio, IA64_XIV_IRQ, ia64_ih_irq);
		if (xiv == 0) {
			/* XXX unlock */
			free(i, M_DEVBUF);
			return (ENOSPC);
		}

		error = intr_event_create(&i->event, (void *)(uintptr_t)xiv,
		    0, irq, ia64_intr_mask, ia64_intr_unmask, ia64_intr_eoi,
		    NULL, "irq%u:", irq);
		if (error) {
			ia64_xiv_free(xiv, IA64_XIV_IRQ);
			/* XXX unlock */
			free(i, M_DEVBUF);
			return (error);
		}

		i->sapic = sa;
		i->irq = irq;
		i->cntp = intrcnt + xiv;
		ia64_intrs[xiv] = i;

		/* XXX unlock */

		sapic_enable(sa, irq, xiv);

		if (name != NULL && *name != '\0') {
			/* XXX needs abstraction. Too error prone. */
			intrname = intrnames + xiv * INTRNAME_LEN;
			memset(intrname, ' ', INTRNAME_LEN - 1);
			bcopy(name, intrname, strlen(name));
		}
	} else {
		i = ia64_intrs[xiv];
		/* XXX unlock */
	}

	KASSERT(i != NULL, ("XIV mapping bug"));

	error = intr_event_add_handler(i->event, name, filter, handler, arg,
	    prio, flags, cookiep);
	return (error);
}

int
ia64_teardown_intr(void *cookie)
{

	return (intr_event_remove_handler(cookie));
}

void
ia64_bind_intr(void)
{
	struct ia64_intr *i;
	struct pcpu *pc;
	u_int xiv;
	int cpu;

	cpu = MAXCPU;
	for (xiv = IA64_NXIVS - 1; xiv >= IA64_MIN_XIV; xiv--) {
		if (ia64_xiv[xiv] != IA64_XIV_IRQ)
			continue;
		i = ia64_intrs[xiv];
		do {
			cpu = (cpu == 0) ? MAXCPU - 1 : cpu - 1;
			pc = cpuid_to_pcpu[cpu];
		} while (pc == NULL || !pc->pc_md.awake);
		sapic_bind_intr(i->irq, pc);
	}
}

/*
 * Interrupt handlers.
 */

void
ia64_handle_intr(struct trapframe *tf)
{
	struct thread *td;
	struct trapframe *stf;
	u_int xiv;

	td = curthread;
	ia64_set_fpsr(IA64_FPSR_DEFAULT);
	PCPU_INC(cnt.v_intr);

	xiv = ia64_get_ivr();
	ia64_srlz_d();
	if (xiv == 15) {
		PCPU_INC(md.stats.pcs_nstrays);
		goto out;
	}

	critical_enter();
	stf = td->td_intr_frame;
	td->td_intr_frame = tf;

	do {
		CTR2(KTR_INTR, "INTR: ITC=%u, XIV=%u",
		    (u_int)tf->tf_special.ifa, xiv);
		if (!(ia64_handler[xiv])(td, xiv, tf)) {
			ia64_set_eoi(0);
			ia64_srlz_d();
		}
		xiv = ia64_get_ivr();
		ia64_srlz_d();
	} while (xiv != 15);

	td->td_intr_frame = stf;
	critical_exit();

 out:
	if (TRAPF_USERMODE(tf)) {
		while (td->td_flags & (TDF_ASTPENDING|TDF_NEEDRESCHED)) {
			ia64_enable_intr();
			ast(tf);
			ia64_disable_intr();
		}
	}
}

static u_int
ia64_ih_invalid(struct thread *td, u_int xiv, struct trapframe *tf)
{

	panic("invalid XIV: %u", xiv);
	return (0);
}

static u_int
ia64_ih_irq(struct thread *td, u_int xiv, struct trapframe *tf)
{
	struct ia64_intr *i;
	struct intr_event *ie;			/* our interrupt event */

	PCPU_INC(md.stats.pcs_nhwints);

	/* Find the interrupt thread for this XIV. */
	i = ia64_intrs[xiv];
	KASSERT(i != NULL, ("%s: unassigned XIV", __func__));

	(*i->cntp)++;

	ie = i->event;
	KASSERT(ie != NULL, ("%s: interrupt without event", __func__));

	if (intr_event_handle(ie, tf) != 0) {
		ia64_intr_mask((void *)(uintptr_t)xiv);
		log(LOG_ERR, "stray irq%u\n", i->irq);
	}

	return (0);
}

#ifdef DDB

static void
db_print_xiv(u_int xiv, int always)
{
	struct ia64_intr *i;

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

DB_SHOW_COMMAND(xiv, db_show_xiv)
{
	u_int xiv;

	if (have_addr) {
		xiv = ((addr >> 4) % 16) * 10 + (addr % 16);
		if (xiv >= IA64_NXIVS)
			db_printf("error: XIV %u not in range [0..%u]\n",
			    xiv, IA64_NXIVS - 1);
		else
			db_print_xiv(xiv, 1);
	} else {
		for (xiv = 0; xiv < IA64_NXIVS; xiv++)
			db_print_xiv(xiv, 0);
	}
}

#endif
