kern_intr.c revision 28440
1/* 2 * Copyright (c) 1997, Stefan Esser <se@freebsd.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice unmodified, this list of conditions, and the following 10 * disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * 26 * $Id: kern_intr.c,v 1.9 1997/08/02 14:31:27 bde Exp $ 27 * 28 */ 29 30#include <sys/types.h> 31#include <sys/malloc.h> 32#include <sys/systm.h> 33#include <sys/errno.h> 34#ifdef RESOURCE_CHECK 35#include <sys/drvresource.h> 36#endif /* RESOURCE_CHECK */ 37 38#include <i386/isa/icu.h> 39#include <i386/isa/intr_machdep.h> 40#include <sys/interrupt.h> 41 42#include <machine/ipl.h> 43 44#include <stddef.h> 45 46typedef struct intrec { 47 intrmask_t mask; 48 inthand2_t *handler; 49 void *argument; 50 struct intrec *next; 51 void *devdata; 52 int intr; 53 intrmask_t *maskptr; 54 int flags; 55} intrec; 56 57/* 58 * The interrupt multiplexer calls each of the handlers in turn, 59 * and applies the associated interrupt mask to "cpl", which is 60 * defined as a ".long" in /sys/i386/isa/ipl.s 61 */ 62 63#ifndef SMP 64static inline intrmask_t 65splq(intrmask_t mask) 66{ 67 intrmask_t tmp = cpl; 68 cpl |= mask; 69 return (tmp); 70} 71#endif /* SMP */ 72 73static void 74intr_mux(void *arg) 75{ 76 intrec *p = arg; 77 78 while (p != NULL) { 79 int oldspl = splq(p->mask); 80 /* inthand2_t should take (void*) argument */ 81 p->handler(p->argument); 82 splx(oldspl); 83 p = p->next; 84 } 85} 86 87/* XXX better use NHWI from <machine/ipl.h> for array size ??? */ 88static intrec *intreclist_head[ICU_LEN]; 89 90static intrec* 91find_idesc(unsigned *maskptr, int irq) 92{ 93 intrec *p = intreclist_head[irq]; 94 95 while (p && p->maskptr != maskptr) 96 p = p->next; 97 98 return (p); 99} 100 101static intrec** 102find_pred(intrec *idesc, int irq) 103{ 104 intrec **pp = &intreclist_head[irq]; 105 intrec *p = *pp; 106 107 while (p != idesc) { 108 if (p == NULL) 109 return (NULL); 110 pp = &p->next; 111 p = *pp; 112 } 113 return (pp); 114} 115 116/* 117 * Both the low level handler and the shared interrupt multiplexer 118 * block out further interrupts as set in the handlers "mask", while 119 * the handler is running. In fact *maskptr should be used for this 120 * purpose, but since this requires one more pointer dereference on 121 * each interrupt, we rather bother update "mask" whenever *maskptr 122 * changes. The function "update_masks" should be called **after** 123 * all manipulation of the linked list of interrupt handlers hung 124 * off of intrdec_head[irq] is complete, since the chain of handlers 125 * will both determine the *maskptr values and the instances of mask 126 * that are fixed. This function should be called with the irq for 127 * which a new handler has been add blocked, since the masks may not 128 * yet know about the use of this irq for a device of a certain class. 129 */ 130 131static void 132update_mux_masks(void) 133{ 134 int irq; 135 for (irq = 0; irq < ICU_LEN; irq++) { 136 intrec *idesc = intreclist_head[irq]; 137 while (idesc != NULL) { 138 if (idesc->maskptr != NULL) { 139 /* our copy of *maskptr may be stale, refresh */ 140 idesc->mask = *idesc->maskptr; 141 } 142 idesc = idesc->next; 143 } 144 } 145} 146 147static void 148update_masks(intrmask_t *maskptr, int irq) 149{ 150 intrmask_t mask = 1 << irq; 151 152 if (maskptr == NULL) 153 return; 154 155 if (find_idesc(maskptr, irq) == NULL) { 156 /* no reference to this maskptr was found in this irq's chain */ 157 if ((*maskptr & mask) == 0) 158 return; 159 /* the irq was included in the classes mask, remove it */ 160 INTRUNMASK(*maskptr, mask); 161 } else { 162 /* a reference to this maskptr was found in this irq's chain */ 163 if ((*maskptr & mask) != 0) 164 return; 165 /* put the irq into the classes mask */ 166 INTRMASK(*maskptr, mask); 167 } 168 /* we need to update all values in the intr_mask[irq] array */ 169 update_intr_masks(); 170 /* update mask in chains of the interrupt multiplex handler as well */ 171 update_mux_masks(); 172} 173 174/* 175 * Add interrupt handler to linked list hung off of intreclist_head[irq] 176 * and install shared interrupt multiplex handler, if necessary 177 */ 178 179static int 180add_intrdesc(intrec *idesc) 181{ 182 int irq = idesc->intr; 183 184 intrec *head = intreclist_head[irq]; 185 186 if (head == NULL) { 187 /* first handler for this irq, just install it */ 188 if (icu_setup(irq, idesc->handler, idesc->argument, 189 idesc->maskptr, idesc->flags) != 0) 190 return (-1); 191 192 update_intrname(irq, idesc->devdata); 193 /* keep reference */ 194 intreclist_head[irq] = idesc; 195 } else { 196 if ((idesc->flags & INTR_EXCL) != 0 197 || (head->flags & INTR_EXCL) != 0) { 198 /* 199 * can't append new handler, if either list head or 200 * new handler do not allow interrupts to be shared 201 */ 202 printf("\tdevice combination doesn't support shared irq%d\n", 203 irq); 204 return (-1); 205 } 206 if (head->next == NULL) { 207 /* 208 * second handler for this irq, replace device driver's 209 * handler by shared interrupt multiplexer function 210 */ 211 icu_unset(irq, head->handler); 212 if (icu_setup(irq, intr_mux, head, 0, 0) != 0) 213 return (-1); 214 if (bootverbose) 215 printf("\tusing shared irq%d.\n", irq); 216 update_intrname(irq, -1); 217 } 218 /* just append to the end of the chain */ 219 while (head->next != NULL) 220 head = head->next; 221 head->next = idesc; 222 } 223 update_masks(idesc->maskptr, irq); 224 return (0); 225} 226 227/* 228 * Add the interrupt handler descriptor data structure created by an 229 * earlier call of create_intr() to the linked list for its irq and 230 * adjust the interrupt masks if necessary. 231 * 232 * This function effectively activates the handler. 233 */ 234 235int 236intr_connect(intrec *idesc) 237{ 238 int errcode = -1; 239 int irq; 240 241#ifdef RESOURCE_CHECK 242 int resflag; 243#endif /* RESOURCE_CHECK */ 244 245 if (idesc == NULL) 246 return (-1); 247 248 irq = idesc->intr; 249#ifdef RESOURCE_CHECK 250 resflag = (idesc->flags & INTR_EXCL) ? RESF_NONE : RESF_SHARED; 251 if (resource_claim(idesc->devdata, REST_INT, resflag, irq, irq) == 0) 252#endif /* RESOURCE_CHECK */ 253 { 254 /* block this irq */ 255 intrmask_t oldspl = splq(1 << irq); 256 257 /* add irq to class selected by maskptr */ 258 errcode = add_intrdesc(idesc); 259 splx(oldspl); 260 } 261 if (errcode != 0) 262 printf("\tintr_connect(irq%d) failed, result=%d\n", 263 irq, errcode); 264 265 return (errcode); 266} 267 268/* 269 * Remove the interrupt handler descriptor data connected created by an 270 * earlier call of intr_connect() from the linked list and adjust the 271 * interrupt masks if necessary. 272 * 273 * This function deactivates the handler. 274 */ 275 276int 277intr_disconnect(intrec *idesc) 278{ 279 intrec **hook, *head; 280 int irq; 281 int errcode = 0; 282 283 if (idesc == NULL) 284 return (-1); 285 286 irq = idesc->intr; 287 288 /* find pointer that keeps the reference to this interrupt descriptor */ 289 hook = find_pred(idesc, irq); 290 if (hook == NULL) 291 return (-1); 292 293 /* make copy of original list head, the line after may overwrite it */ 294 head = intreclist_head[irq]; 295 296 /* unlink: make predecessor point to idesc->next instead of to idesc */ 297 *hook = idesc->next; 298 299 /* now check whether the element we removed was the list head */ 300 if (idesc == head) { 301 intrmask_t oldspl = splq(1 << irq); 302 303 /* we want to remove the list head, which was known to intr_mux */ 304 icu_unset(irq, intr_mux); 305 306 /* check whether the new list head is the only element on list */ 307 head = intreclist_head[irq]; 308 if (head != NULL) { 309 if (head->next != NULL) { 310 /* install the multiplex handler with new list head as argument */ 311 errcode = icu_setup(irq, intr_mux, head, 0, 0); 312 if (errcode == 0) 313 update_intrname(irq, -1); 314 } else { 315 /* install the one remaining handler for this irq */ 316 errcode = icu_setup(irq, head->handler, 317 head->argument, 318 head->maskptr, head->flags); 319 if (errcode == 0) 320 update_intrname(irq, head->devdata); 321 } 322 } 323 splx(oldspl); 324 } 325 update_masks(idesc->maskptr, irq); 326#ifdef RESOURCE_CHECK 327 resource_free(idesc->devdata); 328#endif /* RESOURCE_CHECK */ 329 return (0); 330} 331 332/* 333 * Create an interrupt handler descriptor data structure, which later can 334 * be activated or deactivated at will by calls of [dis]connect(intrec*). 335 * 336 * The dev_instance pointer is required for resource management, and will 337 * only be passed through to resource_claim(). 338 * 339 * The interrupt handler takes an argument of type (void*), which is not 340 * what is currently used for ISA devices. But since the unit number passed 341 * to an ISA interrupt handler can be stored in a (void*) variable, this 342 * causes no problems. Eventually all the ISA interrupt handlers should be 343 * modified to accept the pointer to their private data, too, instead of 344 * an integer index. 345 * 346 * There will be functions that derive a driver and unit name from a 347 * dev_instance variable, and those functions will be used to maintain the 348 * interrupt counter label array referenced by systat and vmstat to report 349 * device interrupt rates (->update_intrlabels). 350 */ 351 352intrec * 353intr_create(void *dev_instance, int irq, inthand2_t handler, void *arg, 354 intrmask_t *maskptr, int flags) 355{ 356 intrec *idesc; 357 358 if (ICU_LEN > 8 * sizeof *maskptr) { 359 printf("create_intr: ICU_LEN of %d too high for %d bit intrmask\n", 360 ICU_LEN, 8 * sizeof *maskptr); 361 return (NULL); 362 } 363 if ((unsigned)irq >= ICU_LEN) { 364 printf("create_intr: requested irq%d too high, limit is %d\n", 365 irq, ICU_LEN -1); 366 return (NULL); 367 } 368 369 idesc = malloc(sizeof *idesc, M_DEVBUF, M_WAITOK); 370 if (idesc) { 371 idesc->next = NULL; 372 bzero(idesc, sizeof *idesc); 373 374 idesc->devdata = dev_instance; 375 idesc->handler = handler; 376 idesc->argument = arg; 377 idesc->maskptr = maskptr; 378 idesc->intr = irq; 379 idesc->flags = flags; 380 } 381 return (idesc); 382} 383 384/* 385 * Return the memory held by the interrupt handler descriptor data structure 386 * to the system. Make sure, the handler is not actively used anymore, before. 387 */ 388 389int 390intr_destroy(intrec *rec) 391{ 392 if (intr_disconnect(rec) != 0) 393 return (-1); 394 free(rec, M_DEVBUF); 395 return (0); 396} 397 398/* 399 * Emulate the register_intr() call previously defined as low level function. 400 * That function (now icu_setup()) may no longer be directly called, since 401 * a conflict between an ISA and PCI interrupt might go by unnocticed, else. 402 */ 403 404int 405register_intr(int intr, int device_id, u_int flags, 406 inthand2_t handler, u_int *maskptr, int unit) 407{ 408 /* XXX modify to include isa_device instead of device_id */ 409 intrec *idesc; 410 411 flags |= INTR_EXCL; 412 idesc = intr_create((void *)device_id, intr, handler, 413 (void*)unit, maskptr, flags); 414 return (intr_connect(idesc)); 415} 416 417/* 418 * Emulate the old unregister_intr() low level function. 419 * Make sure there is just one interrupt, that it was 420 * registered as non-shared, and that the handlers match. 421 */ 422 423int 424unregister_intr(int intr, inthand2_t handler) 425{ 426 intrec *p = intreclist_head[intr]; 427 428 if (p != NULL && (p->flags & INTR_EXCL) != 0 && p->handler == handler) 429 return (intr_destroy(p)); 430 return (EINVAL); 431} 432