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