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.19 1998/08/11 15:08:13 bde Exp $ 27 * 28 */ 29 30 31 #include <sys/types.h> 32 #include <sys/malloc.h> 33 #include <sys/systm.h> 34 #include <sys/errno.h> 35 #ifdef RESOURCE_CHECK 36 #include <sys/drvresource.h> 37 #endif /* RESOURCE_CHECK */ 38 39 #include <machine/ipl.h> 40 41 #ifdef __i386__ 42 #include <i386/isa/icu.h> 43 #include <i386/isa/intr_machdep.h> 44 #endif 45 46 #include <sys/interrupt.h> 47 48 #include <stddef.h> 49 50 #ifdef __i386__ 51 52 typedef struct intrec { 53 intrmask_t mask; 54 inthand2_t *handler; 55 void *argument; 56 struct intrec *next; 57 void *devdata; 58 int intr; 59 intrmask_t *maskptr; 60 int flags; 61 } intrec; 62 63 static intrec *intreclist_head[NHWI]; 64 65 #endif 66 67 struct swilist { 68 swihand_t *sl_handler; 69 struct swilist *sl_next; 70 }; 71 72 static struct swilist swilists[NSWI]; 73 74 #ifdef __i386__ 75 76 /* 77 * The interrupt multiplexer calls each of the handlers in turn, 78 * and applies the associated interrupt mask to "cpl", which is 79 * defined as a ".long" in /sys/i386/isa/ipl.s 80 */ 81 82 #ifndef SMP 83 static __inline intrmask_t 84 splq(intrmask_t mask) 85 { 86 intrmask_t tmp = cpl; 87 cpl |= mask; 88 return (tmp); 89 } 90 #endif /* SMP */ 91 92 static void 93 intr_mux(void *arg) 94 { 95 intrec *p = arg; 96 97 while (p != NULL) { 98 int oldspl = splq(p->mask); 99 p->handler(p->argument); 100 splx(oldspl); 101 p = p->next; 102 } 103 } 104 105 static intrec* 106 find_idesc(unsigned *maskptr, int irq) 107 { 108 intrec *p = intreclist_head[irq]; 109 110 while (p && p->maskptr != maskptr) 111 p = p->next; 112 113 return (p); 114 } 115 116 static intrec** 117 find_pred(intrec *idesc, int irq) 118 { 119 intrec **pp = &intreclist_head[irq]; 120 intrec *p = *pp; 121 122 while (p != idesc) { 123 if (p == NULL) 124 return (NULL); 125 pp = &p->next; 126 p = *pp; 127 } 128 return (pp); 129 } 130 131 /* 132 * Both the low level handler and the shared interrupt multiplexer 133 * block out further interrupts as set in the handlers "mask", while 134 * the handler is running. In fact *maskptr should be used for this 135 * purpose, but since this requires one more pointer dereference on 136 * each interrupt, we rather bother update "mask" whenever *maskptr 137 * changes. The function "update_masks" should be called **after** 138 * all manipulation of the linked list of interrupt handlers hung 139 * off of intrdec_head[irq] is complete, since the chain of handlers 140 * will both determine the *maskptr values and the instances of mask 141 * that are fixed. This function should be called with the irq for 142 * which a new handler has been add blocked, since the masks may not 143 * yet know about the use of this irq for a device of a certain class. 144 */ 145 146 static void 147 update_mux_masks(void) 148 { 149 int irq; 150 for (irq = 0; irq < ICU_LEN; irq++) { 151 intrec *idesc = intreclist_head[irq]; 152 while (idesc != NULL) { 153 if (idesc->maskptr != NULL) { 154 /* our copy of *maskptr may be stale, refresh */ 155 idesc->mask = *idesc->maskptr; 156 } 157 idesc = idesc->next; 158 } 159 } 160 } 161 162 static void 163 update_masks(intrmask_t *maskptr, int irq) 164 { 165 intrmask_t mask = 1 << irq; 166 167 if (maskptr == NULL) 168 return; 169 170 if (find_idesc(maskptr, irq) == NULL) { 171 /* no reference to this maskptr was found in this irq's chain */ 172 if ((*maskptr & mask) == 0) 173 return; 174 /* the irq was included in the classes mask, remove it */ 175 INTRUNMASK(*maskptr, mask); 176 } else { 177 /* a reference to this maskptr was found in this irq's chain */ 178 if ((*maskptr & mask) != 0) 179 return; 180 /* put the irq into the classes mask */ 181 INTRMASK(*maskptr, mask); 182 } 183 /* we need to update all values in the intr_mask[irq] array */ 184 update_intr_masks(); 185 /* update mask in chains of the interrupt multiplex handler as well */ 186 update_mux_masks(); 187 } 188 189 /* 190 * Add interrupt handler to linked list hung off of intreclist_head[irq] 191 * and install shared interrupt multiplex handler, if necessary 192 */ 193 194 static int 195 add_intrdesc(intrec *idesc) 196 { 197 int irq = idesc->intr; 198 199 intrec *head = intreclist_head[irq]; 200 201 if (head == NULL) { 202 /* first handler for this irq, just install it */ 203 if (icu_setup(irq, idesc->handler, idesc->argument, 204 idesc->maskptr, idesc->flags) != 0) 205 return (-1); 206 207 update_intrname(irq, (intptr_t)idesc->devdata); 208 /* keep reference */ 209 intreclist_head[irq] = idesc; 210 } else { 211 if ((idesc->flags & INTR_EXCL) != 0 212 || (head->flags & INTR_EXCL) != 0) { 213 /* 214 * can't append new handler, if either list head or 215 * new handler do not allow interrupts to be shared 216 */ 217 if (bootverbose) 218 printf("\tdevice combination doesn't support " 219 "shared irq%d\n", irq); 220 return (-1); 221 } 222 if (head->next == NULL) { 223 /* 224 * second handler for this irq, replace device driver's 225 * handler by shared interrupt multiplexer function 226 */ 227 icu_unset(irq, head->handler); 228 if (icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0) != 0) 229 return (-1); 230 if (bootverbose) 231 printf("\tusing shared irq%d.\n", irq); 232 update_intrname(irq, -1); 233 } 234 /* just append to the end of the chain */ 235 while (head->next != NULL) 236 head = head->next; 237 head->next = idesc; 238 } 239 update_masks(idesc->maskptr, irq); 240 return (0); 241 } 242 243 /* 244 * Add the interrupt handler descriptor data structure created by an 245 * earlier call of create_intr() to the linked list for its irq and 246 * adjust the interrupt masks if necessary. 247 * 248 * This function effectively activates the handler. 249 */ 250 251 int 252 intr_connect(intrec *idesc) 253 { 254 int errcode = -1; 255 int irq; 256 257 #ifdef RESOURCE_CHECK 258 int resflag; 259 #endif /* RESOURCE_CHECK */ 260 261 if (idesc == NULL) 262 return (-1); 263 264 irq = idesc->intr; 265 #ifdef RESOURCE_CHECK 266 resflag = (idesc->flags & INTR_EXCL) ? RESF_NONE : RESF_SHARED; 267 if (resource_claim(idesc->devdata, REST_INT, resflag, irq, irq) == 0) 268 #endif /* RESOURCE_CHECK */ 269 { 270 /* block this irq */ 271 intrmask_t oldspl = splq(1 << irq); 272 273 /* add irq to class selected by maskptr */ 274 errcode = add_intrdesc(idesc); 275 splx(oldspl); 276 } 277 if (errcode != 0 && bootverbose) 278 printf("\tintr_connect(irq%d) failed, result=%d\n", 279 irq, errcode); 280 281 return (errcode); 282 } 283 284 /* 285 * Remove the interrupt handler descriptor data connected created by an 286 * earlier call of intr_connect() from the linked list and adjust the 287 * interrupt masks if necessary. 288 * 289 * This function deactivates the handler. 290 */ 291 292 int 293 intr_disconnect(intrec *idesc) 294 { 295 intrec **hook, *head; 296 int irq; 297 int errcode = 0; 298 299 if (idesc == NULL) 300 return (-1); 301 302 irq = idesc->intr; 303 304 /* find pointer that keeps the reference to this interrupt descriptor */ 305 hook = find_pred(idesc, irq); 306 if (hook == NULL) 307 return (-1); 308 309 /* make copy of original list head, the line after may overwrite it */ 310 head = intreclist_head[irq]; 311 312 /* unlink: make predecessor point to idesc->next instead of to idesc */ 313 *hook = idesc->next; 314 315 /* now check whether the element we removed was the list head */ 316 if (idesc == head) { 317 intrmask_t oldspl = splq(1 << irq); 318 319 /* we want to remove the list head, which was known to intr_mux */ 320 icu_unset(irq, (inthand2_t*)intr_mux); 321 322 /* check whether the new list head is the only element on list */ 323 head = intreclist_head[irq]; 324 if (head != NULL) { 325 if (head->next != NULL) { 326 /* install the multiplex handler with new list head as argument */ 327 errcode = icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0); 328 if (errcode == 0) 329 update_intrname(irq, -1); 330 } else { 331 /* install the one remaining handler for this irq */ 332 errcode = icu_setup(irq, head->handler, 333 head->argument, 334 head->maskptr, head->flags); 335 if (errcode == 0) 336 update_intrname(irq, (intptr_t)head->devdata); 337 } 338 } 339 splx(oldspl); 340 } 341 update_masks(idesc->maskptr, irq); 342 #ifdef RESOURCE_CHECK 343 resource_free(idesc->devdata); 344 #endif /* RESOURCE_CHECK */ 345 return (0); 346 } 347 348 /* 349 * Create an interrupt handler descriptor data structure, which later can 350 * be activated or deactivated at will by calls of [dis]connect(intrec*). 351 * 352 * The dev_instance pointer is required for resource management, and will 353 * only be passed through to resource_claim(). 354 * 355 * The interrupt handler takes an argument of type (void*), which is not 356 * what is currently used for ISA devices. But since the unit number passed 357 * to an ISA interrupt handler can be stored in a (void*) variable, this 358 * causes no problems. Eventually all the ISA interrupt handlers should be 359 * modified to accept the pointer to their private data, too, instead of 360 * an integer index. 361 * 362 * There will be functions that derive a driver and unit name from a 363 * dev_instance variable, and those functions will be used to maintain the 364 * interrupt counter label array referenced by systat and vmstat to report 365 * device interrupt rates (->update_intrlabels). 366 */ 367 368 intrec * 369 intr_create(void *dev_instance, int irq, inthand2_t handler, void *arg, 370 intrmask_t *maskptr, int flags) 371 { 372 intrec *idesc; 373 374 if (ICU_LEN > 8 * sizeof *maskptr) { 375 printf("create_intr: ICU_LEN of %d too high for %d bit intrmask\n", 376 ICU_LEN, 8 * sizeof *maskptr); 377 return (NULL); 378 } 379 if ((unsigned)irq >= ICU_LEN) { 380 printf("create_intr: requested irq%d too high, limit is %d\n", 381 irq, ICU_LEN -1); 382 return (NULL); 383 } 384 385 idesc = malloc(sizeof *idesc, M_DEVBUF, M_WAITOK); 386 if (idesc) { 387 idesc->next = NULL; 388 bzero(idesc, sizeof *idesc); 389 390 idesc->devdata = dev_instance; 391 idesc->handler = handler; 392 idesc->argument = arg; 393 idesc->maskptr = maskptr; 394 idesc->intr = irq; 395 idesc->flags = flags; 396 } 397 return (idesc); 398 } 399 400 /* 401 * Return the memory held by the interrupt handler descriptor data structure 402 * to the system. Make sure, the handler is not actively used anymore, before. 403 */ 404 405 int 406 intr_destroy(intrec *rec) 407 { 408 if (intr_disconnect(rec) != 0) 409 return (-1); 410 free(rec, M_DEVBUF); 411 return (0); 412 } 413 414 /* 415 * Emulate the register_intr() call previously defined as low level function. 416 * That function (now icu_setup()) may no longer be directly called, since 417 * a conflict between an ISA and PCI interrupt might go by unnocticed, else. 418 */ 419 420 int 421 register_intr(int intr, int device_id, u_int flags, 422 inthand2_t handler, u_int *maskptr, int unit) 423 { 424 /* XXX modify to include isa_device instead of device_id */ 425 intrec *idesc; 426 427 flags |= INTR_EXCL; 428 idesc = intr_create((void *)(intptr_t)device_id, intr, handler, 429 (void*)(intptr_t)unit, maskptr, flags); 430 return (intr_connect(idesc)); 431 } 432 433 /* 434 * Emulate the old unregister_intr() low level function. 435 * Make sure there is just one interrupt, that it was 436 * registered as non-shared, and that the handlers match. 437 */ 438 439 int 440 unregister_intr(int intr, inthand2_t handler) 441 { 442 intrec *p = intreclist_head[intr]; 443 444 if (p != NULL && (p->flags & INTR_EXCL) != 0 && p->handler == handler) 445 return (intr_destroy(p)); 446 return (EINVAL); 447 } 448 449 #endif /* __i386__ */ 450 451 void 452 register_swi(intr, handler) 453 int intr; 454 swihand_t *handler; 455 { 456 struct swilist *slp, *slq; 457 int s; 458 459 if (intr < NHWI || intr >= NHWI + NSWI) 460 panic("register_swi: bad intr %d", intr); 461 if (handler == swi_generic || handler == swi_null) 462 panic("register_swi: bad handler %p", (void *)handler); 463 slp = &swilists[intr - NHWI]; 464 s = splhigh(); 465 if (ihandlers[intr] == swi_null) 466 ihandlers[intr] = handler; 467 else { 468 if (slp->sl_next == NULL) { 469 slp->sl_handler = ihandlers[intr]; 470 ihandlers[intr] = swi_generic; 471 } 472 slq = malloc(sizeof(*slq), M_DEVBUF, M_NOWAIT); 473 if (slq == NULL) 474 panic("register_swi: malloc failed"); 475 slq->sl_handler = handler; 476 slq->sl_next = NULL; 477 while (slp->sl_next != NULL) 478 slp = slp->sl_next; 479 slp->sl_next = slq; 480 } 481 splx(s); 482 } 483 484 void 485 swi_dispatcher(intr) 486 int intr; 487 { 488 struct swilist *slp; 489 490 slp = &swilists[intr - NHWI]; 491 do { 492 (*slp->sl_handler)(); 493 slp = slp->sl_next; 494 } while (slp != NULL); 495 } 496 497 void 498 unregister_swi(intr, handler) 499 int intr; 500 swihand_t *handler; 501 { 502 struct swilist *slfoundpred, *slp, *slq; 503 int s; 504 505 if (intr < NHWI || intr >= NHWI + NSWI) 506 panic("unregister_swi: bad intr %d", intr); 507 if (handler == swi_generic || handler == swi_null) 508 panic("unregister_swi: bad handler %p", (void *)handler); 509 slp = &swilists[intr - NHWI]; 510 s = splhigh(); 511 if (ihandlers[intr] == handler) 512 ihandlers[intr] = swi_null; 513 else if (slp->sl_next != NULL) { 514 slfoundpred = NULL; 515 for (slq = slp->sl_next; slq != NULL; 516 slp = slq, slq = slp->sl_next) 517 if (slq->sl_handler == handler) 518 slfoundpred = slp; 519 slp = &swilists[intr - NHWI]; 520 if (slfoundpred != NULL) { 521 slq = slfoundpred->sl_next; 522 slfoundpred->sl_next = slq->sl_next; 523 free(slq, M_DEVBUF); 524 } else if (slp->sl_handler == handler) { 525 slq = slp->sl_next; 526 slp->sl_next = slq->sl_next; 527 slp->sl_handler = slq->sl_handler; 528 free(slq, M_DEVBUF); 529 } 530 if (slp->sl_next == NULL) 531 ihandlers[intr] = slp->sl_handler; 532 } 533 splx(s); 534 } 535 536