/*	$NetBSD: intr.c,v 1.33 2024/01/19 20:55:42 thorpej Exp $	*/

/*-
 * Copyright (c) 1996 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Adam Glass, Gordon W. Ross, Jason R. Thorpe, and Leo Weppelman.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.33 2024/01/19 20:55:42 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/vmmeter.h>
#include <sys/queue.h>
#include <sys/device.h>
#include <sys/cpu.h>

#include <machine/intr.h>

#define	AVEC_MIN	1
#define	AVEC_MAX	7
#define	AVEC_LOC	25
#define	UVEC_MIN	0
#define	UVEC_MAX	191
#define	UVEC_LOC	64

typedef LIST_HEAD(, intrhand) ih_list_t;
static ih_list_t autovec_list[AVEC_MAX - AVEC_MIN + 1];
static ih_list_t uservec_list[UVEC_MAX - UVEC_MIN + 1];
volatile unsigned int intr_depth;
volatile int ssir;

void
intr_init(void)
{
	int i;

	for (i = 0; i < (AVEC_MAX - AVEC_MIN + 1); ++i) {
		LIST_INIT(&autovec_list[i]);
	}
	for (i = 0; i < (UVEC_MAX - UVEC_MIN + 1); ++i) {
		LIST_INIT(&uservec_list[i]);
	}
}

/*
 * Establish an interrupt vector.
 *   - vector
 *	The vector numer the interrupt should be hooked on. It can either
 *	be an auto-vector or a user-vector.
 *   - type
 *	A bit-wise of:
 *		- AUTO_VEC (mutually exclusive with USER_VEC)
 *			Attach to one of the 7 auto vectors
 *		- USER_VEC (mutually exclusive with AUTO_VEC)
 *			Attach to one of the 192 user vectors
 *		- FAST_VEC
 *			The interrupt function 'ih_fun' will be
 *			put into the 'real' interrupt table. This
 *			means:
 *				- This vector can't be shared
 *				- 'ih_fun' must save registers
 *				- 'ih_fun' must do its own interrupt accounting
 *				- The argument to 'ih_fun' is a standard
 *				  interrupt frame.
 *		- ARG_CLOCKRAME
 *			The 'ih_fun' function will be called with
 *			a standard clock-frame instead of 'ih_arg'.
 *
 *   - pri
 *	When multiple interrupts are established on the same vector,
 *	interrupts with the highest priority will be called first. The
 *	basic ordering is the order of establishment.
 *   - ih_fun
 *	The interrupt function to be called
 *   - ih_arg
 *	The argument given to 'ih_fun' when ARG_CLOCKFRAME is not
 *	specified.
 */

struct intrhand *
intr_establish(int vector, int type, int pri, hw_ifun_t ih_fun, void *ih_arg)
{
	struct intrhand	*ih, *cur_vec;
	ih_list_t	*vec_list;
	u_long		*hard_vec;
	int		s;

	ih = kmem_alloc(sizeof *ih, KM_SLEEP);
	ih->ih_fun    = ih_fun;
	ih->ih_arg    = ih_arg;
	ih->ih_type   = type;
	ih->ih_pri    = pri;
	ih->ih_vector = vector;

	/*
	 * Do some validity checking on the 'vector' argument and determine
	 * vector list this interrupt should be on.
	 */
	switch (type & (AUTO_VEC|USER_VEC)) {
	case AUTO_VEC:
		if (vector < AVEC_MIN || vector > AVEC_MAX) {
			kmem_free(ih, sizeof(*ih));
			return NULL;
		}
		vec_list = &autovec_list[vector - 1];
		hard_vec = &autovects[vector - 1];
		ih->ih_intrcnt = &intrcnt_auto[vector - 1];
		break;
	case USER_VEC:
		if (vector < UVEC_MIN || vector > UVEC_MAX) {
			kmem_free(ih, sizeof(*ih));
			return NULL;
		}
		vec_list = &uservec_list[vector];
		hard_vec = &uservects[vector];
		ih->ih_intrcnt = &intrcnt_user[vector];
		break;
	default:
		printf("%s: bogus vector type\n", __func__);
		kmem_free(ih, sizeof(*ih));
		return NULL;
	}

	/*
	 * If the vec_list is empty, we insert ourselves at the head of the
	 * list and we re-route the 'hard-vector' to the appropriate handler.
	 */
	if (LIST_EMPTY(vec_list)) {

		s = splhigh();
		LIST_INSERT_HEAD(vec_list, ih, ih_link);
		if ((type & FAST_VEC) != 0)
			*hard_vec = (u_long)ih->ih_fun;
		else if (*hard_vec != (u_long)intr_glue) {
			/*
			 * Normally, all settable vectors are already
			 * re-routed to the intr_glue() function. The
			 * marvelous exception to these are the HBL/VBL
			 * interrupts. They happen *very* often and
			 * can't be turned off on the Falcon. So they
			 * are normally vectored to an 'rte' instruction.
			 */
			*hard_vec = (u_long)intr_glue;
		}

		splx(s);

		return ih;
	}

	/*
	 * Check for FAST_VEC botches
	 */
	cur_vec = LIST_FIRST(vec_list);
	if (cur_vec->ih_type & FAST_VEC) {
		kmem_free(ih, sizeof(*ih));
		printf("%s: vector cannot be shared\n", __func__);
		return NULL;
	}

	/*
	 * We traverse the list and place ourselves after any handlers with
	 * our current (or higher) priority level.
	 */
	for (cur_vec = LIST_FIRST(vec_list);
	    LIST_NEXT(cur_vec, ih_link) != NULL;
	    cur_vec = LIST_NEXT(cur_vec, ih_link)) {
		if (ih->ih_pri > cur_vec->ih_pri) {

			s = splhigh();
			LIST_INSERT_BEFORE(cur_vec, ih, ih_link);
			splx(s);

			return ih;
		}
	}

	/*
	 * We're the least important entry, it seems.  We just go
	 * on the end.
	 */
	s = splhigh();
	LIST_INSERT_AFTER(cur_vec, ih, ih_link);
	splx(s);

	return ih;
}

int
intr_disestablish(struct intrhand *ih)
{
	ih_list_t	*vec_list;
	u_long		*hard_vec;
	int		vector, s;
	struct intrhand	*cur_vec;

	vector = ih->ih_vector;
	switch (ih->ih_type & (AUTO_VEC|USER_VEC)) {
	case AUTO_VEC:
		if (vector < AVEC_MIN || vector > AVEC_MAX)
			return 0;
		vec_list = &autovec_list[vector - 1];
		hard_vec = &autovects[vector - 1];
		break;
	case USER_VEC:
		if (vector < UVEC_MIN || vector > UVEC_MAX)
			return 0;
		vec_list = &uservec_list[vector];
		hard_vec = &uservects[vector];
		break;
	default:
		printf("%s: bogus vector type\n", __func__);
		return 0;
	}

	/*
	 * Check if the vector is really in the list we think it's in....
	 */
	for (cur_vec = LIST_FIRST(vec_list);
	    LIST_NEXT(cur_vec, ih_link) != NULL;
	    cur_vec = LIST_NEXT(cur_vec, ih_link)) {
		if (ih == cur_vec)
			break;
	}
	if (ih != cur_vec) {
		printf("%s: 'ih' has inconsistent data\n", __func__);
		return 0;
	}

	s = splhigh();
	LIST_REMOVE(ih, ih_link);
	if (LIST_EMPTY(vec_list) && (ih->ih_type & FAST_VEC) != 0)
		*hard_vec = (u_long)intr_glue;
	splx(s);

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

/*
 * This is the dispatcher called by the low-level
 * assembly language interrupt-glue routine.
 */
void
intr_dispatch(struct clockframe frame)
{
	static int	unexpected, straycount;
	int		vector;
	int		handled = 0;
	ih_list_t	*vec_list;
	struct intrhand	*ih;

	curcpu()->ci_data.cpu_nintr++;
	vector = (frame.cf_vo & 0xfff) >> 2;
	if (vector < (AVEC_LOC + AVEC_MAX) && vector >= AVEC_LOC)
		vec_list = &autovec_list[vector - AVEC_LOC];
	else if (vector <= (UVEC_LOC + UVEC_MAX) && vector >= UVEC_LOC)
		vec_list = &uservec_list[vector - UVEC_LOC];
	else
		panic("%s: Bogus vector %d", __func__, vector);

	if ((ih = LIST_FIRST(vec_list)) == NULL) {
		printf("%s: vector %d unexpected\n", __func__, vector);
		if (++unexpected > 10)
			panic("%s: too many unexpected interrupts", __func__);
		return;
	}
	ih->ih_intrcnt[0]++;

	/* Give all the handlers a chance. */
	for (; ih != NULL; ih = LIST_NEXT(ih, ih_link))
		handled |= (*ih->ih_fun)((ih->ih_type & ARG_CLOCKFRAME) ?
		    &frame : ih->ih_arg, frame.cf_sr);

	if (handled)
		straycount = 0;
	else if (++straycount > 50)
		panic("%s: too many stray interrupts", __func__);
	else
		printf("%s: stray level %d interrupt\n", __func__, vector);
}

bool
cpu_intr_p(void)
{

	return intr_depth != 0;
}

const uint16_t ipl2psl_table[NIPL] = {
	[IPL_NONE]       = PSL_S | PSL_IPL0,
	[IPL_SOFTCLOCK]  = PSL_S | PSL_IPL1,
	[IPL_SOFTBIO]    = PSL_S | PSL_IPL1,
	[IPL_SOFTNET]    = PSL_S | PSL_IPL1,
	[IPL_SOFTSERIAL] = PSL_S | PSL_IPL1,
	[IPL_VM]         = PSL_S | PSL_IPL4,
	[IPL_SCHED]      = PSL_S | PSL_IPL6,
	[IPL_HIGH]       = PSL_S | PSL_IPL7,
};
