summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/nmi.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>1999-04-21 07:26:30 +0000
committerpeter <peter@FreeBSD.org>1999-04-21 07:26:30 +0000
commitfa628c268c2d531552037f05699c06959d9618e6 (patch)
tree139882de7181a80226508d0a7c0f4695f2639ae0 /sys/i386/isa/nmi.c
parent47b96f90c086096cb12e76442461d4da895e207b (diff)
downloadFreeBSD-src-fa628c268c2d531552037f05699c06959d9618e6.zip
FreeBSD-src-fa628c268c2d531552037f05699c06959d9618e6.tar.gz
Stage 1 of a cleanup of the i386 interrupt registration mechanism.
Interrupts under the new scheme are managed by the i386 nexus with the awareness of the resource manager. There is further room for optimizing the interfaces still. All the users of register_intr()/intr_create() should be gone, with the exception of pcic and i386/isa/clock.c.
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