diff options
author | se <se@FreeBSD.org> | 1997-05-26 14:37:43 +0000 |
---|---|---|
committer | se <se@FreeBSD.org> | 1997-05-26 14:37:43 +0000 |
commit | a201b8ac68687823a59787419197aa1c621886d1 (patch) | |
tree | 5832d876f0ad27b2c4a969a16ab4820dc144a85a /sys/kern/kern_intr.c | |
parent | 386d11add89232aea3218b58907c3ade3be330e7 (diff) | |
download | FreeBSD-src-a201b8ac68687823a59787419197aa1c621886d1.zip FreeBSD-src-a201b8ac68687823a59787419197aa1c621886d1.tar.gz |
Add support for shared interrupts to the kernel. This code is meant
be (eventually) architecture independent. It provides an emulation
of the ISA interrupt registration function register_intr(), but that
function does no longer manipulated the interrupt controller and
interrupt descriptor table, but calls the architecture dependent
function setup_icu() for that purpose.
After theISA/EISA bus code has been modified to directly call the new
interrupt registartion functions (intr_create() and intr_connect()),
the emulation of register_intr() should be dropped.
The C level interrupt handler function should take a (void*) argument,
and the function pointer type (inthand2_t) should defined in some other
place than isa_device.h.
This commit is a pre-requisite for the removal of the PCI specific shared
interrupt code.
Reviewed by: dfr,bde
Diffstat (limited to 'sys/kern/kern_intr.c')
-rw-r--r-- | sys/kern/kern_intr.c | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/sys/kern/kern_intr.c b/sys/kern/kern_intr.c new file mode 100644 index 0000000..fa17cf1 --- /dev/null +++ b/sys/kern/kern_intr.c @@ -0,0 +1,418 @@ +/* + * 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$ + * + */ + +#include <sys/types.h> +#include <sys/malloc.h> +#include <sys/time.h> +#include <sys/systm.h> +#include <sys/errno.h> +#ifdef RESOURCE_CHECK +#include <sys/drvresource.h> +#endif /* RESOURCE_CHECK */ + +#include <i386/isa/icu.h> +#include <i386/isa/isa_device.h> +#include <sys/interrupt.h> /* XXX needs inthand2_t from isa_device.h */ + +#include <machine/spl.h> + +#include <stddef.h> + +#include "vector.h" + +/* + * 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/icu.s + */ + +static inline intrmask +splq(intrmask mask) +{ + intrmask tmp = cpl; + cpl |= mask; + return (tmp); +} + +static void +intr_mux(void *arg) +{ + intrec *p = arg; + + while (p != NULL) { + int oldspl = splq(p->mask); + /* inthand2_t should take (void*) argument */ + p->handler(p->argument); + splx(oldspl); + p = p->next; + } +} + +/* XXX better use NHWI from <machine/ipl.h> for array size ??? */ +static intrec *intreclist_head[ICU_LEN]; + +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 *maskptr, int irq) +{ + intrmask 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->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 + */ + 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, -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 oldspl = splq(1 << irq); + + /* add irq to class selected by maskptr */ + errcode = add_intrdesc(idesc); + splx(oldspl); + } + if (errcode != 0) + 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 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->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, -1); + } else if (head != NULL) { + /* 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->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 *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 *)device_id, intr, handler, + (void*)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); +} |