summaryrefslogtreecommitdiffstats
path: root/sys/kern/kern_intr.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/kern/kern_intr.c')
-rw-r--r--sys/kern/kern_intr.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/sys/kern/kern_intr.c b/sys/kern/kern_intr.c
new file mode 100644
index 0000000..1d6756c
--- /dev/null
+++ b/sys/kern/kern_intr.c
@@ -0,0 +1,535 @@
+/*
+ * 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.20 1998/09/26 14:25:31 dfr 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);
+}
+
OpenPOWER on IntegriCloud