1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * Autovectored Interrupt Configuration and Deconfiguration 31 */ 32 33 #include <sys/param.h> 34 #include <sys/cmn_err.h> 35 #include <sys/trap.h> 36 #include <sys/t_lock.h> 37 #include <sys/avintr.h> 38 #include <sys/kmem.h> 39 #include <sys/machlock.h> 40 #include <sys/systm.h> 41 #include <sys/machsystm.h> 42 #include <sys/sunddi.h> 43 #include <sys/x_call.h> 44 #include <sys/cpuvar.h> 45 #include <sys/atomic.h> 46 #include <sys/smp_impldefs.h> 47 #include <sys/sdt.h> 48 #include <sys/stack.h> 49 #include <sys/ddi_impldefs.h> 50 51 static int insert_av(void *intr_id, struct av_head *vectp, avfunc f, 52 caddr_t arg1, caddr_t arg2, int pri_level, dev_info_t *dip); 53 static void remove_av(void *intr_id, struct av_head *vectp, avfunc f, 54 int pri_level, int vect); 55 56 /* 57 * Arrange for a driver to be called when a particular 58 * auto-vectored interrupt occurs. 59 * NOTE: if a device can generate interrupts on more than 60 * one level, or if a driver services devices that interrupt 61 * on more than one level, then the driver should install 62 * itself on each of those levels. 63 */ 64 static char badsoft[] = 65 "add_avintr: bad soft interrupt level %d for driver '%s'\n"; 66 static char multilevel[] = 67 "!IRQ%d is being shared by drivers with different interrupt levels.\n" 68 "This may result in reduced system performance."; 69 static char multilevel2[] = 70 "Cannot register interrupt for '%s' device at IPL %d because it\n" 71 "conflicts with another device using the same vector %d with an IPL\n" 72 "of %d. Reconfigure the conflicting devices to use different vectors."; 73 74 #define MAX_VECT 256 75 struct autovec *nmivect = NULL; 76 struct av_head autovect[MAX_VECT]; 77 struct av_head softvect[LOCK_LEVEL + 1]; 78 kmutex_t av_lock; 79 ddi_softint_hdl_impl_t softlevel1_hdl = 80 {0, NULL, NULL, 0, 0, NULL, NULL, NULL}; 81 82 void 83 set_pending(int pri) 84 { 85 atomic_or_32((uint32_t *)&CPU->cpu_softinfo.st_pending, 1 << pri); 86 } 87 88 /* 89 * register nmi interrupt routine. The first arg is used only to order 90 * various nmi interrupt service routines in the chain. Higher lvls will 91 * be called first 92 */ 93 int 94 add_nmintr(int lvl, avfunc nmintr, char *name, caddr_t arg) 95 { 96 struct autovec *mem; 97 struct autovec *p, *prev = NULL; 98 99 if (nmintr == NULL) { 100 printf("Attempt to add null vect for %s on nmi\n", name); 101 return (0); 102 103 } 104 105 mem = kmem_zalloc(sizeof (struct autovec), KM_SLEEP); 106 mem->av_vector = nmintr; 107 mem->av_intarg1 = arg; 108 mem->av_intarg2 = NULL; 109 mem->av_intr_id = NULL; 110 mem->av_prilevel = lvl; 111 mem->av_link = NULL; 112 113 mutex_enter(&av_lock); 114 115 if (!nmivect) { 116 nmivect = mem; 117 mutex_exit(&av_lock); 118 return (1); 119 } 120 /* find where it goes in list */ 121 for (p = nmivect; p != NULL; p = p->av_link) { 122 if (p->av_vector == nmintr && p->av_intarg1 == arg) { 123 /* 124 * already in list 125 * So? Somebody added the same interrupt twice. 126 */ 127 cmn_err(CE_WARN, "Driver already registered '%s'", 128 name); 129 kmem_free(mem, sizeof (struct autovec)); 130 mutex_exit(&av_lock); 131 return (0); 132 } 133 if (p->av_prilevel < lvl) { 134 if (p == nmivect) { /* it's at head of list */ 135 mem->av_link = p; 136 nmivect = mem; 137 } else { 138 mem->av_link = p; 139 prev->av_link = mem; 140 } 141 mutex_exit(&av_lock); 142 return (1); 143 } 144 prev = p; 145 146 } 147 /* didn't find it, add it to the end */ 148 prev->av_link = mem; 149 mutex_exit(&av_lock); 150 return (1); 151 152 } 153 154 /* 155 * register a hardware interrupt handler. 156 */ 157 int 158 add_avintr(void *intr_id, int lvl, avfunc xxintr, char *name, int vect, 159 caddr_t arg1, caddr_t arg2, dev_info_t *dip) 160 { 161 struct av_head *vecp = (struct av_head *)0; 162 avfunc f; 163 int s, vectindex; /* save old spl value */ 164 ushort_t hi_pri; 165 166 if ((f = xxintr) == NULL) { 167 printf("Attempt to add null vect for %s on vector %d\n", 168 name, vect); 169 return (0); 170 171 } 172 vectindex = vect % MAX_VECT; 173 174 vecp = &autovect[vectindex]; 175 176 /* 177 * "hi_pri == 0" implies all entries on list are "unused", 178 * which means that it's OK to just insert this one. 179 */ 180 hi_pri = vecp->avh_hi_pri; 181 if (vecp->avh_link && (hi_pri != 0)) { 182 if (((hi_pri > LOCK_LEVEL) && (lvl < LOCK_LEVEL)) || 183 ((hi_pri < LOCK_LEVEL) && (lvl > LOCK_LEVEL))) { 184 cmn_err(CE_WARN, multilevel2, name, lvl, vect, 185 hi_pri); 186 return (0); 187 } 188 if ((vecp->avh_lo_pri != lvl) || (hi_pri != lvl)) 189 cmn_err(CE_NOTE, multilevel, vect); 190 } 191 192 if (!insert_av(intr_id, vecp, f, arg1, arg2, lvl, dip)) 193 return (0); 194 s = splhi(); 195 /* 196 * do what ever machine specific things are necessary 197 * to set priority level (e.g. set picmasks) 198 */ 199 mutex_enter(&av_lock); 200 (*addspl)(vect, lvl, vecp->avh_lo_pri, vecp->avh_hi_pri); 201 mutex_exit(&av_lock); 202 splx(s); 203 return (1); 204 205 } 206 207 void 208 update_avsoftintr_args(void *intr_id, int lvl, caddr_t arg2) 209 { 210 struct autovec *p; 211 struct autovec *target = NULL; 212 struct av_head *vectp = (struct av_head *)&softvect[lvl]; 213 214 for (p = vectp->avh_link; p && p->av_vector; p = p->av_link) { 215 if (p->av_intr_id == intr_id) { 216 target = p; 217 break; 218 } 219 } 220 221 if (target == NULL) 222 return; 223 target->av_intarg2 = arg2; 224 } 225 226 /* 227 * Register a software interrupt handler 228 */ 229 int 230 add_avsoftintr(void *intr_id, int lvl, avfunc xxintr, char *name, 231 caddr_t arg1, caddr_t arg2) 232 { 233 int slvl; 234 235 if ((slvl = slvltovect(lvl)) != -1) 236 return (add_avintr(intr_id, lvl, xxintr, 237 name, slvl, arg1, arg2, NULL)); 238 239 if (intr_id == NULL) { 240 printf("Attempt to add null intr_id for %s on level %d\n", 241 name, lvl); 242 return (0); 243 } 244 245 if (xxintr == NULL) { 246 printf("Attempt to add null handler for %s on level %d\n", 247 name, lvl); 248 return (0); 249 } 250 251 if (lvl <= 0 || lvl > LOCK_LEVEL) { 252 printf(badsoft, lvl, name); 253 return (0); 254 } 255 if (!insert_av(intr_id, &softvect[lvl], xxintr, arg1, arg2, 256 lvl, NULL)) { 257 return (0); 258 } 259 return (1); 260 } 261 262 /* insert an interrupt vector into chain */ 263 static int 264 insert_av(void *intr_id, struct av_head *vectp, avfunc f, caddr_t arg1, 265 caddr_t arg2, int pri_level, dev_info_t *dip) 266 { 267 /* 268 * Protect rewrites of the list 269 */ 270 struct autovec *p, *mem; 271 272 mem = kmem_zalloc(sizeof (struct autovec), KM_SLEEP); 273 mem->av_vector = f; 274 mem->av_intarg1 = arg1; 275 mem->av_intarg2 = arg2; 276 mem->av_intr_id = intr_id; 277 mem->av_prilevel = pri_level; 278 mem->av_dip = dip; 279 mem->av_link = NULL; 280 281 mutex_enter(&av_lock); 282 283 if (vectp->avh_link == NULL) { /* Nothing on list - put it at head */ 284 vectp->avh_link = mem; 285 vectp->avh_hi_pri = vectp->avh_lo_pri = (ushort_t)pri_level; 286 287 mutex_exit(&av_lock); 288 return (1); 289 } 290 291 /* find where it goes in list */ 292 for (p = vectp->avh_link; p != NULL; p = p->av_link) { 293 if (p->av_vector == NULL) { /* freed struct available */ 294 kmem_free(mem, sizeof (struct autovec)); 295 p->av_intarg1 = arg1; 296 p->av_intarg2 = arg2; 297 p->av_intr_id = intr_id; 298 p->av_prilevel = pri_level; 299 if (pri_level > (int)vectp->avh_hi_pri) { 300 vectp->avh_hi_pri = (ushort_t)pri_level; 301 } 302 if (pri_level < (int)vectp->avh_lo_pri) { 303 vectp->avh_lo_pri = (ushort_t)pri_level; 304 } 305 p->av_vector = f; 306 mutex_exit(&av_lock); 307 return (1); 308 } 309 } 310 /* insert new intpt at beginning of chain */ 311 mem->av_link = vectp->avh_link; 312 vectp->avh_link = mem; 313 if (pri_level > (int)vectp->avh_hi_pri) { 314 vectp->avh_hi_pri = (ushort_t)pri_level; 315 } 316 if (pri_level < (int)vectp->avh_lo_pri) { 317 vectp->avh_lo_pri = (ushort_t)pri_level; 318 } 319 mutex_exit(&av_lock); 320 321 return (1); 322 } 323 324 /* 325 * Remove a driver from the autovector list. 326 */ 327 int 328 rem_avsoftintr(void *intr_id, int lvl, avfunc xxintr) 329 { 330 struct av_head *vecp = (struct av_head *)0; 331 int slvl; 332 333 if (xxintr == NULL) 334 return (0); 335 336 if ((slvl = slvltovect(lvl)) != -1) { 337 rem_avintr(intr_id, lvl, xxintr, slvl); 338 return (1); 339 } 340 341 if (lvl <= 0 && lvl >= LOCK_LEVEL) { 342 return (0); 343 } 344 vecp = &softvect[lvl]; 345 remove_av(intr_id, vecp, xxintr, lvl, 0); 346 347 return (1); 348 } 349 350 void 351 rem_avintr(void *intr_id, int lvl, avfunc xxintr, int vect) 352 { 353 struct av_head *vecp = (struct av_head *)0; 354 avfunc f; 355 int s, vectindex; /* save old spl value */ 356 357 if ((f = xxintr) == NULL) 358 return; 359 360 vectindex = vect % MAX_VECT; 361 vecp = &autovect[vectindex]; 362 remove_av(intr_id, vecp, f, lvl, vect); 363 s = splhi(); 364 mutex_enter(&av_lock); 365 (*delspl)(vect, lvl, vecp->avh_lo_pri, vecp->avh_hi_pri); 366 mutex_exit(&av_lock); 367 splx(s); 368 } 369 370 371 /* 372 * After having made a change to an autovector list, wait until we have 373 * seen each cpu not executing an interrupt at that level--so we know our 374 * change has taken effect completely (no old state in registers, etc). 375 */ 376 void 377 wait_till_seen(int ipl) 378 { 379 int cpu_in_chain, cix; 380 struct cpu *cpup; 381 cpuset_t cpus_to_check; 382 383 CPUSET_ALL(cpus_to_check); 384 do { 385 cpu_in_chain = 0; 386 for (cix = 0; cix < NCPU; cix++) { 387 cpup = cpu[cix]; 388 if (cpup != NULL && CPU_IN_SET(cpus_to_check, cix)) { 389 if (intr_active(cpup, ipl)) { 390 cpu_in_chain = 1; 391 } else { 392 CPUSET_DEL(cpus_to_check, cix); 393 } 394 } 395 } 396 } while (cpu_in_chain); 397 } 398 399 /* remove an interrupt vector from the chain */ 400 static void 401 remove_av(void *intr_id, struct av_head *vectp, avfunc f, int pri_level, 402 int vect) 403 { 404 struct autovec *endp, *p, *target; 405 int lo_pri, hi_pri; 406 int ipl; 407 /* 408 * Protect rewrites of the list 409 */ 410 target = NULL; 411 412 mutex_enter(&av_lock); 413 ipl = pri_level; 414 lo_pri = MAXIPL; 415 hi_pri = 0; 416 for (endp = p = vectp->avh_link; p && p->av_vector; p = p->av_link) { 417 endp = p; 418 if ((p->av_vector == f) && (p->av_intr_id == intr_id)) { 419 /* found the handler */ 420 target = p; 421 continue; 422 } 423 if (p->av_prilevel > hi_pri) 424 hi_pri = p->av_prilevel; 425 if (p->av_prilevel < lo_pri) 426 lo_pri = p->av_prilevel; 427 } 428 if (ipl < hi_pri) 429 ipl = hi_pri; 430 if (target == NULL) { /* not found */ 431 printf("Couldn't remove function %p at %d, %d\n", 432 (void *)f, vect, pri_level); 433 mutex_exit(&av_lock); 434 return; 435 } 436 437 target->av_vector = NULL; 438 wait_till_seen(ipl); 439 if (endp != target) { /* vector to be removed is not last in chain */ 440 target->av_intarg1 = endp->av_intarg1; 441 target->av_intarg2 = endp->av_intarg2; 442 target->av_prilevel = endp->av_prilevel; 443 target->av_intr_id = endp->av_intr_id; 444 target->av_vector = endp->av_vector; 445 /* 446 * We have a hole here where the routine corresponding to 447 * endp may not get called. Do a wait_till_seen to take care 448 * of this. 449 */ 450 wait_till_seen(ipl); 451 endp->av_vector = NULL; 452 } 453 454 if (lo_pri > hi_pri) { /* the chain is now empty */ 455 /* Leave the unused entries here for probable future use */ 456 vectp->avh_lo_pri = MAXIPL; 457 vectp->avh_hi_pri = 0; 458 } else { 459 if ((int)vectp->avh_lo_pri < lo_pri) 460 vectp->avh_lo_pri = (ushort_t)lo_pri; 461 if ((int)vectp->avh_hi_pri > hi_pri) 462 vectp->avh_hi_pri = (ushort_t)hi_pri; 463 } 464 mutex_exit(&av_lock); 465 wait_till_seen(ipl); 466 } 467 468 /* 469 * Trigger a soft interrupt. 470 */ 471 void 472 siron(void) 473 { 474 softlevel1_hdl.ih_pending = 1; 475 (*setsoftint)(1); 476 } 477 478 /* 479 * Walk the autovector table for this vector, invoking each 480 * interrupt handler as we go. 481 */ 482 void 483 av_dispatch_autovect(uint_t vec) 484 { 485 struct autovec *av; 486 487 ASSERT_STACK_ALIGNED(); 488 489 while ((av = autovect[vec].avh_link) != NULL) { 490 uint_t numcalled = 0; 491 uint_t claimed = 0; 492 493 for (; av; av = av->av_link) { 494 uint_t r; 495 uint_t (*intr)() = av->av_vector; 496 caddr_t arg1 = av->av_intarg1; 497 caddr_t arg2 = av->av_intarg2; 498 dev_info_t *dip = av->av_dip; 499 500 numcalled++; 501 if (intr == NULL) 502 break; 503 504 DTRACE_PROBE4(interrupt__start, dev_info_t *, dip, 505 void *, intr, caddr_t, arg1, caddr_t, arg2); 506 r = (*intr)(arg1, arg2); 507 DTRACE_PROBE4(interrupt__complete, dev_info_t *, dip, 508 void *, intr, caddr_t, arg1, uint_t, r); 509 claimed |= r; 510 } 511 512 /* 513 * If there's only one interrupt handler in the chain, 514 * or if no-one claimed the interrupt at all give up now. 515 */ 516 if (numcalled == 1 || claimed == 0) 517 break; 518 } 519 } 520 521 /* 522 * Call every soft interrupt handler we can find at this level once. 523 */ 524 void 525 av_dispatch_softvect(uint_t pil) 526 { 527 struct autovec *av; 528 ddi_softint_hdl_impl_t *hdlp; 529 uint_t (*intr)(); 530 caddr_t arg1; 531 caddr_t arg2; 532 533 ASSERT_STACK_ALIGNED(); 534 ASSERT(pil >= 0 && pil <= PIL_MAX); 535 536 for (av = softvect[pil].avh_link; av; av = av->av_link) { 537 if ((intr = av->av_vector) == NULL) 538 break; 539 arg1 = av->av_intarg1; 540 arg2 = av->av_intarg2; 541 542 hdlp = (ddi_softint_hdl_impl_t *)av->av_intr_id; 543 ASSERT(hdlp); 544 545 if (hdlp->ih_pending) { 546 hdlp->ih_pending = 0; 547 (void) (*intr)(arg1, arg2); 548 } 549 } 550 } 551 552 struct regs; 553 554 /* 555 * Call every NMI handler we know of once. 556 */ 557 void 558 av_dispatch_nmivect(struct regs *rp) 559 { 560 struct autovec *av; 561 562 ASSERT_STACK_ALIGNED(); 563 564 for (av = nmivect; av; av = av->av_link) 565 (void) (av->av_vector)(av->av_intarg1, rp); 566 } 567