summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/nmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386/isa/nmi.c')
-rw-r--r--sys/i386/isa/nmi.c468
1 files changed, 426 insertions, 42 deletions
diff --git a/sys/i386/isa/nmi.c b/sys/i386/isa/nmi.c
index 4f7c1e9..256c617 100644
--- a/sys/i386/isa/nmi.c
+++ b/sys/i386/isa/nmi.c
@@ -34,7 +34,13 @@
* SUCH DAMAGE.
*
* from: @(#)isa.c 7.2 (Berkeley) 5/13/91
- * $Id: intr_machdep.c,v 1.17 1999/04/14 14:26:36 bde Exp $
+ * $Id: intr_machdep.c,v 1.18 1999/04/16 21:22:22 peter Exp $
+ */
+/*
+ * This file contains an aggregated module marked:
+ * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
+ * All rights reserved.
+ * See the notice for details.
*/
#include "opt_auto_eoi.h"
@@ -45,9 +51,14 @@
#endif
#include <sys/systm.h>
#include <sys/syslog.h>
+#include <sys/malloc.h>
+#include <sys/errno.h>
+#include <sys/interrupt.h>
#include <machine/ipl.h>
#include <machine/md_var.h>
#include <machine/segments.h>
+#include <sys/bus.h>
+
#if defined(APIC_IO)
#include <machine/smp.h>
#include <machine/smptests.h> /** FAST_HI */
@@ -62,6 +73,7 @@
#endif
#include <i386/isa/icu.h>
+#include <isa/isavar.h>
#include <i386/isa/intr_machdep.h>
#include <sys/interrupt.h>
#ifdef APIC_IO
@@ -300,7 +312,8 @@ update_intr_masks(void)
if (intr==ICU_SLAVEID) continue; /* ignore 8259 SLAVE output */
#endif /* APIC_IO */
maskptr = intr_mptr[intr];
- if (!maskptr) continue;
+ if (!maskptr)
+ continue;
*maskptr |= 1 << intr;
mask = *maskptr;
if (mask != intr_mask[intr]) {
@@ -316,48 +329,11 @@ update_intr_masks(void)
return (n);
}
-static const char *
-isa_get_nameunit(int id)
-{
- static char buf[32];
- struct isa_device *dp;
-
- if (id == -1)
- return ("pci"); /* XXX may also be eisa */
- if (id == 0)
- return ("clk0"); /* XXX may also be sloppy driver */
- if (id == 1)
- return ("rtc0");
-#if 0
- for (dp = isa_devtab_bio; dp->id_driver != NULL; dp++)
- if (dp->id_id == id)
- goto found_device;
- for (dp = isa_devtab_cam; dp->id_driver != NULL; dp++)
- if (dp->id_id == id)
- goto found_device;
- for (dp = isa_devtab_net; dp->id_driver != NULL; dp++)
- if (dp->id_id == id)
- goto found_device;
- for (dp = isa_devtab_null; dp->id_driver != NULL; dp++)
- if (dp->id_id == id)
- goto found_device;
- for (dp = isa_devtab_tty; dp->id_driver != NULL; dp++)
- if (dp->id_id == id)
- goto found_device;
-#endif
- return "???";
-
-found_device:
- snprintf(buf, sizeof(buf), "%s%d", dp->id_driver->name, dp->id_unit);
- return (buf);
-}
-
-void
-update_intrname(int intr, int device_id)
+static void
+update_intrname(int intr, char *name)
{
char buf[32];
char *cp;
- const char *name;
int name_index, off, strayintr;
/*
@@ -371,7 +347,8 @@ update_intrname(int intr, int device_id)
strayintr) + 1;
}
- name = isa_get_nameunit(device_id);
+ if (name == NULL)
+ name = "???";
if (snprintf(buf, sizeof(buf), "%s irq%d", name, intr) >= sizeof(buf))
goto use_bitbucket;
@@ -516,3 +493,410 @@ icu_unset(intr, handler)
write_eflags(ef);
return (0);
}
+
+/* The following notice applies beyond this point in the file */
+
+/*
+ * 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 $
+ *
+ */
+
+typedef struct intrec {
+ intrmask_t mask;
+ inthand2_t *handler;
+ void *argument;
+ struct intrec *next;
+ char *name;
+ int intr;
+ intrmask_t *maskptr;
+ int flags;
+} intrec;
+
+static intrec *intreclist_head[ICU_LEN];
+
+typedef struct isarec {
+ int id_unit;
+ ointhand2_t *id_handler;
+} isarec;
+
+static isarec *isareclist[ICU_LEN];
+
+/*
+ * 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
+ */
+
+static void
+intr_mux(void *arg)
+{
+ intrec *p = arg;
+ int oldspl;
+
+ while (p != NULL) {
+ oldspl = splq(p->mask);
+ p->handler(p->argument);
+ splx(oldspl);
+ p = p->next;
+ }
+}
+
+static void
+isa_intr_wrap(void *cookie)
+{
+ isarec *irec = (isarec *)cookie;
+
+ irec->id_handler(irec->id_unit);
+}
+
+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, idesc->name);
+ /* 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, intr_mux, head, 0, 0) != 0)
+ return (-1);
+ if (bootverbose)
+ printf("\tusing shared irq%d.\n", irq);
+ update_intrname(irq, "mux");
+ }
+ /* 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);
+}
+
+/*
+ * Create and activate an interrupt handler descriptor data structure.
+ *
+ * The dev_instance pointer is required for resource management, and will
+ * only be passed through to resource_claim().
+ *
+ * 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).
+ *
+ * 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.
+ */
+
+intrec *
+inthand_add(const char *name, int irq, inthand2_t handler, void *arg,
+ intrmask_t *maskptr, int flags)
+{
+ intrec *idesc;
+ int errcode = -1;
+ intrmask_t oldspl;
+
+ 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 == NULL)
+ return NULL;
+ bzero(idesc, sizeof *idesc);
+
+ if (name == NULL)
+ name = "???";
+ idesc->name = malloc(strlen(name) + 1, M_DEVBUF, M_WAITOK);
+ if (idesc->name == NULL) {
+ free(idesc, M_DEVBUF);
+ return NULL;
+ }
+ strcpy(idesc->name, name);
+
+ idesc->handler = handler;
+ idesc->argument = arg;
+ idesc->maskptr = maskptr;
+ idesc->intr = irq;
+ idesc->flags = flags;
+
+ /* block this irq */
+ oldspl = splq(1 << irq);
+
+ /* add irq to class selected by maskptr */
+ errcode = add_intrdesc(idesc);
+ splx(oldspl);
+
+ if (errcode != 0) {
+ if (bootverbose)
+ printf("\tintr_connect(irq%d) failed, result=%d\n",
+ irq, errcode);
+ free(idesc->name, M_DEVBUF);
+ free(idesc, M_DEVBUF);
+ idesc = NULL;
+ }
+
+ return (idesc);
+}
+
+/*
+ * Deactivate and remove the interrupt handler descriptor data connected
+ * created by an earlier call of intr_connect() from the linked list and
+ * adjust theinterrupt masks if necessary.
+ *
+ * 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
+inthand_remove(intrec *idesc)
+{
+ intrec **hook, *head;
+ int irq;
+ int errcode = 0;
+ intrmask_t oldspl;
+
+ 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) {
+
+ oldspl = splq(1 << irq);
+
+ /* we want to remove the list head, which was known to intr_mux */
+ icu_unset(irq, 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, intr_mux, head, 0, 0);
+ if (errcode == 0)
+ update_intrname(irq, NULL);
+ } 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, head->name);
+ }
+ }
+ splx(oldspl);
+ }
+ update_masks(idesc->maskptr, irq);
+
+ free(idesc, 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,
+ ointhand2_t handler, u_int *maskptr, int unit)
+{
+ intrec *idesc;
+ isarec *irec;
+
+ irec = malloc(sizeof *irec, M_DEVBUF, M_WAITOK);
+ if (irec == NULL)
+ return NULL;
+ bzero(irec, sizeof *irec);
+ irec->id_unit = device_id;
+ irec->id_handler = handler;
+
+ flags |= INTR_EXCL;
+ idesc = inthand_add("old", intr, isa_intr_wrap, irec, maskptr, flags);
+ if (idesc == NULL) {
+ free(irec, M_DEVBUF);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * 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, ointhand2_t handler)
+{
+ intrec *p = intreclist_head[intr];
+
+ if (p != NULL && (p->flags & INTR_EXCL) != 0 &&
+ p->handler == isa_intr_wrap && isareclist[intr] != NULL &&
+ isareclist[intr]->id_handler == handler) {
+ free(isareclist[intr], M_DEVBUF);
+ isareclist[intr] = NULL;
+ return (inthand_remove(p));
+ }
+ return (EINVAL);
+}
OpenPOWER on IntegriCloud