freebsd-dev/sys/kern/kern_intr.c

433 lines
12 KiB
C

/*
* 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.13 1998/02/10 17:10:23 eivind Exp $
*
*/
#include <sys/types.h>
#include <sys/malloc.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/intr_machdep.h>
#include <sys/interrupt.h>
#include <stddef.h>
typedef struct intrec {
intrmask_t mask;
inthand2_t *handler;
void *argument;
struct intrec *next;
void *devdata;
int intr;
intrmask_t *maskptr;
int flags;
} intrec;
/*
* 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
#include <machine/ipl.h>
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);
/* inthand2_t should take (void*) argument */
p->handler((int)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_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, (int)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, (int)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 *)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);
}