/*
 * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
 * 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 unmodified, 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 ``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 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.
 *
 * $Id: kern_intr.c,v 1.21 1998/11/10 09:16:29 peter Exp $
 *
 */


#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#ifdef RESOURCE_CHECK
#include <sys/drvresource.h>
#endif /* RESOURCE_CHECK */

#include <machine/ipl.h>

#ifdef __i386__
#include <i386/isa/icu.h>
#include <i386/isa/intr_machdep.h>
#endif

#include <sys/interrupt.h>

#include <stddef.h>

#ifdef __i386__

typedef struct intrec {
	intrmask_t	mask;
	inthand2_t	*handler;
	void		*argument;
	struct intrec	*next;
	void		*devdata;
	int		intr;
	intrmask_t	*maskptr;
	int		flags;
} intrec;

static intrec *intreclist_head[NHWI];

#endif

struct swilist {
	swihand_t	*sl_handler;
	struct swilist	*sl_next;
};

static struct swilist swilists[NSWI];

#ifdef __i386__

/*
 * The interrupt multiplexer calls each of the handlers in turn,
 * and applies the associated interrupt mask to "cpl", which is
 * defined as a ".long" in /sys/i386/isa/ipl.s
 */

#ifndef SMP
static __inline intrmask_t
splq(intrmask_t mask)
{
	intrmask_t tmp = cpl;
	cpl |= mask;
	return (tmp);
}
#endif /* SMP */

static void
intr_mux(void *arg)
{
	intrec *p = arg;

	while (p != NULL) {
		int oldspl = splq(p->mask);
		p->handler(p->argument);
		splx(oldspl);
		p = p->next;
	}
}

static intrec*
find_idesc(unsigned *maskptr, int irq)
{
	intrec *p = intreclist_head[irq];

	while (p && p->maskptr != maskptr)
		p = p->next;

	return (p);
}

static intrec**
find_pred(intrec *idesc, int irq)
{
	intrec **pp = &intreclist_head[irq];
	intrec *p = *pp;

	while (p != idesc) {
		if (p == NULL)
			return (NULL);
		pp = &p->next;
		p = *pp;
	}
	return (pp);
}

/*
 * Both the low level handler and the shared interrupt multiplexer
 * block out further interrupts as set in the handlers "mask", while
 * the handler is running. In fact *maskptr should be used for this
 * purpose, but since this requires one more pointer dereference on
 * each interrupt, we rather bother update "mask" whenever *maskptr
 * changes. The function "update_masks" should be called **after**
 * all manipulation of the linked list of interrupt handlers hung
 * off of intrdec_head[irq] is complete, since the chain of handlers
 * will both determine the *maskptr values and the instances of mask
 * that are fixed. This function should be called with the irq for
 * which a new handler has been add blocked, since the masks may not
 * yet know about the use of this irq for a device of a certain class.
 */

static void
update_mux_masks(void)
{
	int irq;
	for (irq = 0; irq < ICU_LEN; irq++) {
		intrec *idesc = intreclist_head[irq];
		while (idesc != NULL) {
			if (idesc->maskptr != NULL) {
				/* our copy of *maskptr may be stale, refresh */
				idesc->mask = *idesc->maskptr;
			}
			idesc = idesc->next;
		}
	}
}

static void
update_masks(intrmask_t *maskptr, int irq)
{
	intrmask_t mask = 1 << irq;

	if (maskptr == NULL)
		return;

	if (find_idesc(maskptr, irq) == NULL) {
		/* no reference to this maskptr was found in this irq's chain */
		if ((*maskptr & mask) == 0)
			return;
		/* the irq was included in the classes mask, remove it */
		INTRUNMASK(*maskptr, mask);
	} else {
		/* a reference to this maskptr was found in this irq's chain */
		if ((*maskptr & mask) != 0)
			return;
		/* put the irq into the classes mask */
		INTRMASK(*maskptr, mask);
	}
	/* we need to update all values in the intr_mask[irq] array */
	update_intr_masks();
	/* update mask in chains of the interrupt multiplex handler as well */
	update_mux_masks();
}

/*
 * Add interrupt handler to linked list hung off of intreclist_head[irq]
 * and install shared interrupt multiplex handler, if necessary
 */

static int
add_intrdesc(intrec *idesc)
{
	int irq = idesc->intr;

	intrec *head = intreclist_head[irq];

	if (head == NULL) {
		/* first handler for this irq, just install it */
		if (icu_setup(irq, idesc->handler, idesc->argument, 
			      idesc->maskptr, idesc->flags) != 0)
			return (-1);

		update_intrname(irq, (intptr_t)idesc->devdata);
		/* keep reference */
		intreclist_head[irq] = idesc;
	} else {
		if ((idesc->flags & INTR_EXCL) != 0
		    || (head->flags & INTR_EXCL) != 0) {
			/*
			 * can't append new handler, if either list head or
			 * new handler do not allow interrupts to be shared
			 */
			if (bootverbose)
				printf("\tdevice combination doesn't support "
				       "shared irq%d\n", irq);
			return (-1);
		}
		if (head->next == NULL) {
			/*
			 * second handler for this irq, replace device driver's
			 * handler by shared interrupt multiplexer function
			 */
			icu_unset(irq, head->handler);
			if (icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0) != 0)
				return (-1);
			if (bootverbose)
				printf("\tusing shared irq%d.\n", irq);
			update_intrname(irq, -1);
		}
		/* just append to the end of the chain */
		while (head->next != NULL)
			head = head->next;
		head->next = idesc;
	}
	update_masks(idesc->maskptr, irq);
	return (0);
}

/*
 * Add the interrupt handler descriptor data structure created by an
 * earlier call of create_intr() to the linked list for its irq and
 * adjust the interrupt masks if necessary.
 *
 * This function effectively activates the handler.
 */

int
intr_connect(intrec *idesc)
{
	int errcode = -1;
	int irq;

#ifdef RESOURCE_CHECK
	int resflag;
#endif /* RESOURCE_CHECK */

	if (idesc == NULL)
		return (-1);

	irq = idesc->intr;
#ifdef RESOURCE_CHECK
	resflag = (idesc->flags & INTR_EXCL) ? RESF_NONE : RESF_SHARED;
	if (resource_claim(idesc->devdata, REST_INT, resflag, irq, irq) == 0)
#endif /* RESOURCE_CHECK */
	{
		/* block this irq */
		intrmask_t oldspl = splq(1 << irq);

		/* add irq to class selected by maskptr */
		errcode = add_intrdesc(idesc);
		splx(oldspl);
	}
	if (errcode != 0 && bootverbose)
		printf("\tintr_connect(irq%d) failed, result=%d\n", 
		       irq, errcode);

	return (errcode);
}

/*
 * Remove the interrupt handler descriptor data connected created by an
 * earlier call of intr_connect() from the linked list and adjust the
 * interrupt masks if necessary.
 *
 * This function deactivates the handler.
 */

int
intr_disconnect(intrec *idesc)
{
	intrec **hook, *head;
	int irq;
	int errcode = 0;

	if (idesc == NULL)
		return (-1);

	irq = idesc->intr;

	/* find pointer that keeps the reference to this interrupt descriptor */
	hook = find_pred(idesc, irq);
	if (hook == NULL)
		return (-1);

	/* make copy of original list head, the line after may overwrite it */
	head = intreclist_head[irq];

	/* unlink: make predecessor point to idesc->next instead of to idesc */
	*hook = idesc->next;

	/* now check whether the element we removed was the list head */
	if (idesc == head) {
		intrmask_t oldspl = splq(1 << irq);

		/* we want to remove the list head, which was known to intr_mux */
		icu_unset(irq, (inthand2_t*)intr_mux);

		/* check whether the new list head is the only element on list */
		head = intreclist_head[irq];
		if (head != NULL) {
			if (head->next != NULL) {
				/* install the multiplex handler with new list head as argument */
				errcode = icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0);
				if (errcode == 0)
					update_intrname(irq, -1);
			} else {
				/* install the one remaining handler for this irq */
				errcode = icu_setup(irq, head->handler,
						    head->argument,
						    head->maskptr, head->flags);
				if (errcode == 0)
					update_intrname(irq, (intptr_t)head->devdata);
			}
		}
		splx(oldspl);
	}
	update_masks(idesc->maskptr, irq);
#ifdef RESOURCE_CHECK
	resource_free(idesc->devdata);
#endif /* RESOURCE_CHECK */
	return (0);
}

/*
 * Create an interrupt handler descriptor data structure, which later can
 * be activated or deactivated at will by calls of [dis]connect(intrec*).
 *
 * The dev_instance pointer is required for resource management, and will
 * only be passed through to resource_claim().
 *
 * The interrupt handler takes an argument of type (void*), which is not
 * what is currently used for ISA devices. But since the unit number passed
 * to an ISA interrupt handler can be stored in a (void*) variable, this
 * causes no problems. Eventually all the ISA interrupt handlers should be
 * modified to accept the pointer to their private data, too, instead of
 * an integer index.
 *
 * There will be functions that derive a driver and unit name from a
 * dev_instance variable, and those functions will be used to maintain the
 * interrupt counter label array referenced by systat and vmstat to report
 * device interrupt rates (->update_intrlabels).
 */

intrec *
intr_create(void *dev_instance, int irq, inthand2_t handler, void *arg,
	     intrmask_t *maskptr, int flags)
{
	intrec *idesc;

	if (ICU_LEN > 8 * sizeof *maskptr) {
		printf("create_intr: ICU_LEN of %d too high for %d bit intrmask\n",
		       ICU_LEN, 8 * sizeof *maskptr);
		return (NULL);
	}
	if ((unsigned)irq >= ICU_LEN) {
		printf("create_intr: requested irq%d too high, limit is %d\n",
		       irq, ICU_LEN -1);
		return (NULL);
	}

	idesc = malloc(sizeof *idesc, M_DEVBUF, M_WAITOK);
	if (idesc) {
		idesc->next     = NULL;
		bzero(idesc, sizeof *idesc);

		idesc->devdata  = dev_instance;
		idesc->handler  = handler;
		idesc->argument = arg;
		idesc->maskptr  = maskptr;
		idesc->intr     = irq;
		idesc->flags    = flags;
	}
	return (idesc);
}

/*
 * Return the memory held by the interrupt handler descriptor data structure
 * to the system. Make sure, the handler is not actively used anymore, before.
 */

int
intr_destroy(intrec *rec)
{
	if (intr_disconnect(rec) != 0)
		return (-1);
	free(rec, M_DEVBUF);
	return (0);
}

/*
 * Emulate the register_intr() call previously defined as low level function.
 * That function (now icu_setup()) may no longer be directly called, since 
 * a conflict between an ISA and PCI interrupt might go by unnocticed, else.
 */

int
register_intr(int intr, int device_id, u_int flags,
	      inthand2_t handler, u_int *maskptr, int unit)
{
	/* XXX modify to include isa_device instead of device_id */
	intrec *idesc;

	flags |= INTR_EXCL;
	idesc = intr_create((void *)(intptr_t)device_id, intr, handler, 
			    (void*)(intptr_t)unit, maskptr, flags);
	return (intr_connect(idesc));
}

/*
 * Emulate the old unregister_intr() low level function. 
 * Make sure there is just one interrupt, that it was 
 * registered as non-shared, and that the handlers match.
 */

int
unregister_intr(int intr, inthand2_t handler)
{
	intrec *p = intreclist_head[intr];

	if (p != NULL && (p->flags & INTR_EXCL) != 0 && p->handler == handler)
		return (intr_destroy(p));
	return (EINVAL);
}

#endif /* __i386__ */

void
register_swi(intr, handler)
	int intr;
	swihand_t *handler;
{
	struct swilist *slp, *slq;
	int s;

	if (intr < NHWI || intr >= NHWI + NSWI)
		panic("register_swi: bad intr %d", intr);
	if (handler == swi_generic || handler == swi_null)
		panic("register_swi: bad handler %p", (void *)handler);
	slp = &swilists[intr - NHWI];
	s = splhigh();
	if (ihandlers[intr] == swi_null)
		ihandlers[intr] = handler;
	else {
		if (slp->sl_next == NULL) {
			slp->sl_handler = ihandlers[intr];
			ihandlers[intr] = swi_generic;
		}
		slq = malloc(sizeof(*slq), M_DEVBUF, M_NOWAIT);
		if (slq == NULL)
			panic("register_swi: malloc failed");
		slq->sl_handler = handler;
		slq->sl_next = NULL;
		while (slp->sl_next != NULL)
			slp = slp->sl_next;
		slp->sl_next = slq;
	}
	splx(s);
}

void
swi_dispatcher(intr)
	int intr;
{
	struct swilist *slp;

	slp = &swilists[intr - NHWI];
	do {
		(*slp->sl_handler)();
		slp = slp->sl_next;
	} while (slp != NULL);
}

void
unregister_swi(intr, handler)
	int intr;
	swihand_t *handler;
{
	struct swilist *slfoundpred, *slp, *slq;
	int s;

	if (intr < NHWI || intr >= NHWI + NSWI)
		panic("unregister_swi: bad intr %d", intr);
	if (handler == swi_generic || handler == swi_null)
		panic("unregister_swi: bad handler %p", (void *)handler);
	slp = &swilists[intr - NHWI];
	s = splhigh();
	if (ihandlers[intr] == handler)
		ihandlers[intr] = swi_null;
	else if (slp->sl_next != NULL) {
		slfoundpred = NULL;
		for (slq = slp->sl_next; slq != NULL;
		    slp = slq, slq = slp->sl_next)
			if (slq->sl_handler == handler)
				slfoundpred = slp;
		slp = &swilists[intr - NHWI];
		if (slfoundpred != NULL) {
			slq = slfoundpred->sl_next;
			slfoundpred->sl_next = slq->sl_next;
			free(slq, M_DEVBUF);
		} else if (slp->sl_handler == handler) {
			slq = slp->sl_next;
			slp->sl_next = slq->sl_next;
			slp->sl_handler = slq->sl_handler;
			free(slq, M_DEVBUF);
		}
		if (slp->sl_next == NULL)
			ihandlers[intr] = slp->sl_handler;
	}
	splx(s);
}

