/* $NetBSD: interrupt.c,v 1.23 1998/02/24 07:38:01 thorpej 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.23 1998/02/24 07:38:01 thorpej Exp $");*/
__FBSDID("$FreeBSD: src/sys/alpha/alpha/interrupt.c,v 1.89 2005/12/22 22:16:04 jhb Exp $");

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

#include <machine/bwx.h>
#include <machine/cpuconf.h>
#include <machine/frame.h>
#include <machine/intr.h>
#include <machine/md_var.h>
#include <machine/reg.h>
#include <machine/rpb.h>
#include <machine/smp.h>

#ifdef EVCNT_COUNTERS
struct evcnt clock_intr_evcnt;	/* event counter for clock intrs. */
#else
#include <machine/intrcnt.h>
#endif

volatile int mc_expected, mc_received;

static void 
dummy_perf(unsigned long vector, struct trapframe *framep)  
{
	printf("performance interrupt!\n");
}

void (*perf_irq)(unsigned long, struct trapframe *) = dummy_perf;


static u_int schedclk2;
static void alpha_clock_interrupt(struct trapframe *framep);

void
interrupt(a0, a1, a2, framep)
	unsigned long a0, a1, a2;
	struct trapframe *framep;
{
	struct thread *td;
#ifdef SMP
	register_t s;
#endif

	/*
	 * Find our per-cpu globals.
	 */
#ifdef SMP
	s = intr_disable();
#endif
	pcpup = (struct pcpu *) alpha_pal_rdval();
	td = curthread;
#ifdef SMP
	td->td_md.md_kernnest++;
	intr_restore(s);
#endif
	atomic_add_int(&td->td_intr_nesting_level, 1);

#if KSTACK_GUARD_PAGES == 0
#ifndef SMP
	{
		if ((caddr_t) framep < (caddr_t) td->td_pcb + 1024) {
			panic("possible stack overflow\n");
		}
	}
#endif
#endif

	framep->tf_regs[FRAME_TRAPARG_A0] = a0;
	framep->tf_regs[FRAME_TRAPARG_A1] = a1;
	framep->tf_regs[FRAME_TRAPARG_A2] = a2;
	switch (a0) {
#ifdef SMP
	case ALPHA_INTR_XPROC:	/* interprocessor interrupt */
		CTR0(KTR_INTR|KTR_SMP, "interprocessor interrupt");
		smp_handle_ipi(framep); /* note: lock not taken */
		break;
#endif
		
	case ALPHA_INTR_CLOCK:	/* clock interrupt */
		CTR0(KTR_INTR, "clock interrupt");
		alpha_clock_interrupt(framep);
		break;

	case ALPHA_INTR_ERROR:	/* Machine Check or Correctable Error */
		a0 = alpha_pal_rdmces();
		if (platform.mcheck_handler)
			(*platform.mcheck_handler)(a0, framep, a1, a2);
		else
			machine_check(a0, framep, a1, a2);
		break;

	case ALPHA_INTR_DEVICE:	/* I/O device interrupt */
		PCPU_LAZY_INC(cnt.v_intr);
		if (platform.iointr)
			(*platform.iointr)(framep, a1);
		break;

	case ALPHA_INTR_PERF:	/* interprocessor interrupt */
		perf_irq(a1, framep);
		break;

	case ALPHA_INTR_PASSIVE:
#if	0
		printf("passive release interrupt vec 0x%lx (ignoring)\n", a1);
#endif
		break;

	default:
		panic("unexpected interrupt: type 0x%lx vec 0x%lx a2 0x%lx\n",
		    a0, a1, a2);
		/* NOTREACHED */
	}
	atomic_subtract_int(&td->td_intr_nesting_level, 1);
}

void
set_iointr(niointr)
	void (*niointr)(void *, unsigned long);
{
	if (platform.iointr)
		panic("set iointr twice");
	platform.iointr = niointr;
}


void
machine_check(mces, framep, vector, param)
	unsigned long mces;
	struct trapframe *framep;
	unsigned long vector, param;
{
	const char *type;

	/* Make sure it's an error we know about. */
	if ((mces & (ALPHA_MCES_MIP|ALPHA_MCES_SCE|ALPHA_MCES_PCE)) == 0) {
		type = "fatal machine check or error (unknown type)";
		goto fatal;
	}

	/* Machine checks. */
	if (mces & ALPHA_MCES_MIP) {
		/* If we weren't expecting it, then we punt. */
		if (!mc_expected) {
			type = "unexpected machine check";
			goto fatal;
		}

		mc_expected = 0;
		mc_received = 1;
	}

	/* System correctable errors. */
	if (mces & ALPHA_MCES_SCE)
		printf("Warning: received system correctable error.\n");

	/* Processor correctable errors. */
	if (mces & ALPHA_MCES_PCE)
		printf("Warning: received processor correctable error.\n"); 

	/* Clear pending machine checks and correctable errors */
	alpha_pal_wrmces(mces);
	return;

fatal:
	/* Clear pending machine checks and correctable errors */
	alpha_pal_wrmces(mces);

	printf("\n");
	printf("%s:\n", type);
	printf("\n");
	printf("    mces    = 0x%lx\n", mces);
	printf("    vector  = 0x%lx\n", vector);
	printf("    param   = 0x%lx\n", param);
	printf("    pc      = 0x%lx\n", framep->tf_regs[FRAME_PC]);
	printf("    ra      = 0x%lx\n", framep->tf_regs[FRAME_RA]);
	printf("    curproc = %p\n", curproc);
	if (curproc != NULL)
		printf("        pid = %d, comm = %s\n", curproc->p_pid,
		    curproc->p_comm);
	printf("\n");
#ifdef KDB
	kdb_trap(ALPHA_KENTRY_MM, mces, framep);
#endif
	panic("machine check");
}

int
badaddr(addr, size)
	void *addr;
	size_t size;
{
	return(badaddr_read(addr, size, NULL));
}

int
badaddr_read(addr, size, rptr)
	void *addr;
	size_t size;
	void *rptr;
{
	long rcpt;

	/* Get rid of any stale machine checks that have been waiting.  */
	alpha_pal_draina();

	/* Tell the trap code to expect a machine check. */
	mc_received = 0;
	mc_expected = 1;

	/* Read from the test address, and make sure the read happens. */
	alpha_mb();
	switch (size) {
	case sizeof (u_int8_t):
		if (alpha_implver() >= ALPHA_IMPLVER_EV5
		    && alpha_amask(ALPHA_AMASK_BWX) == 0)
			rcpt = ldbu((vm_offset_t)addr);
		else
			rcpt = *(volatile u_int8_t *)addr;
		break;

	case sizeof (u_int16_t):
		if (alpha_implver() >= ALPHA_IMPLVER_EV5
		    && alpha_amask(ALPHA_AMASK_BWX) == 0)
			rcpt = ldwu((vm_offset_t)addr);
		else
			rcpt = *(volatile u_int16_t *)addr;
		break;

	case sizeof (u_int32_t):
		rcpt = *(volatile u_int32_t *)addr;
		break;

	case sizeof (u_int64_t):
		rcpt = *(volatile u_int64_t *)addr;
		break;

	default:
		panic("badaddr: invalid size (%ld)\n", size);
	}
	alpha_mb();
	alpha_mb(); /* magic for ev5 2100A  & maybe more */

	/* Make sure we took the machine check, if we caused one. */
	alpha_pal_draina();

	/* disallow further machine checks */
	mc_expected = 0;

	if (rptr && mc_received == 0) {
		switch (size) {
		case sizeof (u_int8_t):
			*(volatile u_int8_t *)rptr = rcpt;
			break;

		case sizeof (u_int16_t):
			*(volatile u_int16_t *)rptr = rcpt;
			break;

		case sizeof (u_int32_t):
			*(volatile u_int32_t *)rptr = rcpt;
			break;

		case sizeof (u_int64_t):
			*(volatile u_int64_t *)rptr = rcpt;
			break;
		}
	}
	/* Return non-zero (i.e. true) if it's a bad address. */
	return (mc_received);
}

#define HASHVEC(vector)	((vector) % 31)

LIST_HEAD(alpha_intr_list, alpha_intr);

struct alpha_intr {
    LIST_ENTRY(alpha_intr) list; /* chain handlers in this hash bucket */
    uintptr_t		vector;	/* vector to match */
    struct intr_event	*ie;    /* interrupt event structure */
    volatile long	*cntp;  /* interrupt counter */
    void		(*disable)(uintptr_t);
};

static struct mtx alpha_intr_hash_lock;
static struct alpha_intr_list alpha_intr_hash[31];

static void	ithds_init(void *dummy);

static void
ithds_init(void *dummy)
{

	mtx_init(&alpha_intr_hash_lock, "intr table", NULL, MTX_SPIN);
}
SYSINIT(ithds_init, SI_SUB_INTR, SI_ORDER_SECOND, ithds_init, NULL);

int
alpha_setup_intr(const char *name, uintptr_t vector, driver_intr_t handler, void *arg,
		 enum intr_type flags, void **cookiep, volatile long *cntp,
    		 void (*disable)(uintptr_t), void (*enable)(uintptr_t))
{
	int h = HASHVEC(vector);
	struct alpha_intr *i;
	int errcode;

	/*
	 * XXX - Can we have more than one device on a vector?  If so, we have
	 * a race condition here that needs to be worked around similar to
	 * the fashion done in the i386 inthand_add() function.
	 */
	
	/* First, check for an existing hash table entry for this vector. */
	mtx_lock_spin(&alpha_intr_hash_lock);
	for (i = LIST_FIRST(&alpha_intr_hash[h]); i && i->vector != vector;
	    i = LIST_NEXT(i, list))
		;	/* nothing */
	mtx_unlock_spin(&alpha_intr_hash_lock);

	if (i == NULL) {
		/* None was found, so create an entry. */
		i = malloc(sizeof(struct alpha_intr), M_DEVBUF, M_NOWAIT);
		if (i == NULL)
			return ENOMEM;
		i->vector = vector;
		i->cntp = cntp;
		i->disable = disable;
		errcode = intr_event_create(&i->ie, (void *)vector, 0,
		    (void (*)(void *))enable, "intr:");
		if (errcode) {
			free(i, M_DEVBUF);
			return errcode;
		}

		mtx_lock_spin(&alpha_intr_hash_lock);
		LIST_INSERT_HEAD(&alpha_intr_hash[h], i, list);
		mtx_unlock_spin(&alpha_intr_hash_lock);
	}

	/* Second, add this handler. */
	return (intr_event_add_handler(i->ie, name, handler, arg,
	    intr_priority(flags), flags, cookiep));
}

int
alpha_teardown_intr(void *cookie)
{

	return (intr_event_remove_handler(cookie));
}

/*
 * XXX: Alpha doesn't count stray interrupts like some of the other archs.
 */
void
alpha_dispatch_intr(void *frame, unsigned long vector)
{
	int h = HASHVEC(vector);
	struct alpha_intr *i;
	struct intr_event *ie;
	struct intr_handler *ih;
	int error, thread;

	/*
	 * Walk the hash bucket for this vector looking for this vector's
	 * interrupt structure.
	 */
	for (i = LIST_FIRST(&alpha_intr_hash[h]); i && i->vector != vector;
	    i = LIST_NEXT(i, list))
		;	/* nothing */

	/* No interrupt structure for this vector. */
	if (i == NULL)
		return;

	ie = i->ie;
	KASSERT(ie != NULL, ("interrupt structure without an event"));

	/*
	 * As an optimization, if an event has no handlers, don't
	 * schedule it to run.
	 */
	if (TAILQ_EMPTY(&ie->ie_handlers))
		return;

	atomic_add_long(i->cntp, 1);

	/*
	 * It seems that we need to return from an interrupt back to PAL
	 * on the same CPU that received the interrupt, so pin the interrupted
	 * thread to the current CPU until we return from the interrupt.
	 */
	sched_pin();

	/* Execute all fast interrupt handlers directly. */	
	thread = 0;
	critical_enter();
	TAILQ_FOREACH(ih, &ie->ie_handlers, ih_next) {
		if (!(ih->ih_flags & IH_FAST)) {
			thread = 1;
			continue;
		}
		CTR4(KTR_INTR, "%s: exec %p(%p) for %s", __func__,
		    ih->ih_handler, ih->ih_argument, ih->ih_name);
		ih->ih_handler(ih->ih_argument);
	}
	critical_exit();

	/*
	 * If the ithread needs to run, disable the source and schedule the
	 * thread.
	 */
	if (thread) {
		if (i->disable) {
			CTR1(KTR_INTR,
			    "alpha_dispatch_intr: disabling vector 0x%x",
			    i->vector);
			i->disable(i->vector);
		}
		error = intr_event_schedule_thread(ie);
		KASSERT(error == 0, ("got an impossible stray interrupt"));
	}
	sched_unpin();
}

static void
alpha_clock_interrupt(struct trapframe *framep)
{

	PCPU_LAZY_INC(cnt.v_intr);
#ifdef EVCNT_COUNTERS
	clock_intr_evcnt.ev_count++;
#else
	intrcnt[INTRCNT_CLOCK]++;
#endif
	if (platform.clockintr) {
		critical_enter();
#ifdef SMP
		/*
		 * Only one processor drives the actual timer.
		 */
		if (PCPU_GET(cpuid) == 0) {
#endif
			(*platform.clockintr)(TRAPF_USERMODE(framep),
			    TRAPF_PC(framep));

			/* Bump stathz divider. */
			schedclk2++;
#ifdef SMP
		} else
			hardclock_cpu(TRAPF_USERMODE(framep));
#endif
		if (profprocs != 0)
			profclock(TRAPF_USERMODE(framep), TRAPF_PC(framep));

		/* divide hz (1024) by 8 to get stathz (128) */
		if ((schedclk2 & 0x7) == 0)
			statclock(TRAPF_USERMODE(framep));
		critical_exit();
	}
}
