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 * $FreeBSD$ 27 * 28 */ 29 30 31 #include <sys/param.h> 32 #include <sys/bus.h> 33 #include <sys/rtprio.h> 34 #include <sys/systm.h> 35 #include <sys/interrupt.h> 36 #include <sys/kernel.h> 37 #include <sys/kthread.h> 38 #include <sys/ktr.h> 39 #include <sys/lock.h> 40 #include <sys/malloc.h> 41 #include <sys/mutex.h> 42 #include <sys/proc.h> 43 #include <sys/random.h> 44 #include <sys/resourcevar.h> 45 #include <sys/sysctl.h> 46 #include <sys/unistd.h> 47 #include <sys/vmmeter.h> 48 #include <machine/atomic.h> 49 #include <machine/cpu.h> 50 #include <machine/md_var.h> 51 #include <machine/stdarg.h> 52 53 #include <net/netisr.h> /* prototype for legacy_setsoftnet */ 54 55 struct int_entropy { 56 struct proc *proc; 57 int vector; 58 }; 59 60 void *net_ih; 61 void *vm_ih; 62 void *softclock_ih; 63 struct ithd *clk_ithd; 64 struct ithd *tty_ithd; 65 66 static MALLOC_DEFINE(M_ITHREAD, "ithread", "Interrupt Threads"); 67 68 static void ithread_update(struct ithd *); 69 static void ithread_loop(void *); 70 static void start_softintr(void *); 71 static void swi_net(void *); 72 73 u_char 74 ithread_priority(enum intr_type flags) 75 { 76 u_char pri; 77 78 flags &= (INTR_TYPE_TTY | INTR_TYPE_BIO | INTR_TYPE_NET | 79 INTR_TYPE_CAM | INTR_TYPE_MISC | INTR_TYPE_CLK | INTR_TYPE_AV); 80 switch (flags) { 81 case INTR_TYPE_TTY: 82 pri = PI_TTYLOW; 83 break; 84 case INTR_TYPE_BIO: 85 /* 86 * XXX We need to refine this. BSD/OS distinguishes 87 * between tape and disk priorities. 88 */ 89 pri = PI_DISK; 90 break; 91 case INTR_TYPE_NET: 92 pri = PI_NET; 93 break; 94 case INTR_TYPE_CAM: 95 pri = PI_DISK; /* XXX or PI_CAM? */ 96 break; 97 case INTR_TYPE_AV: /* Audio/video */ 98 pri = PI_AV; 99 break; 100 case INTR_TYPE_CLK: 101 pri = PI_REALTIME; 102 break; 103 case INTR_TYPE_MISC: 104 pri = PI_DULL; /* don't care */ 105 break; 106 default: 107 /* We didn't specify an interrupt level. */ 108 panic("ithread_priority: no interrupt type in flags"); 109 } 110 111 return pri; 112 } 113 114 /* 115 * Regenerate the name (p_comm) and priority for a threaded interrupt thread. 116 */ 117 static void 118 ithread_update(struct ithd *ithd) 119 { 120 struct intrhand *ih; 121 struct thread *td; 122 struct proc *p; 123 int entropy; 124 125 mtx_assert(&ithd->it_lock, MA_OWNED); 126 td = ithd->it_td; 127 if (td == NULL) 128 return; 129 p = td->td_proc; 130 131 strncpy(p->p_comm, ithd->it_name, sizeof(ithd->it_name)); 132 ih = TAILQ_FIRST(&ithd->it_handlers); 133 if (ih == NULL) { 134 td->td_ksegrp->kg_pri.pri_level = PRI_MAX_ITHD; 135 ithd->it_flags &= ~IT_ENTROPY; 136 return; 137 } 138 139 entropy = 0; 140 td->td_ksegrp->kg_pri.pri_level = ih->ih_pri; 141 td->td_ksegrp->kg_pri.pri_native = ih->ih_pri; 142 TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) { 143 if (strlen(p->p_comm) + strlen(ih->ih_name) + 1 < 144 sizeof(p->p_comm)) { 145 strcat(p->p_comm, " "); 146 strcat(p->p_comm, ih->ih_name); 147 } else if (strlen(p->p_comm) + 1 == sizeof(p->p_comm)) { 148 if (p->p_comm[sizeof(p->p_comm) - 2] == '+') 149 p->p_comm[sizeof(p->p_comm) - 2] = '*'; 150 else 151 p->p_comm[sizeof(p->p_comm) - 2] = '+'; 152 } else 153 strcat(p->p_comm, "+"); 154 if (ih->ih_flags & IH_ENTROPY) 155 entropy++; 156 } 157 158 if (entropy) 159 ithd->it_flags |= IT_ENTROPY; 160 else 161 ithd->it_flags &= ~IT_ENTROPY; 162 163 CTR1(KTR_INTR, __func__ ": updated %s\n", p->p_comm); 164 } 165 166 int 167 ithread_create(struct ithd **ithread, int vector, int flags, 168 void (*disable)(int), void (*enable)(int), const char *fmt, ...) 169 { 170 struct ithd *ithd; 171 struct thread *td; 172 struct proc *p; 173 int error; 174 va_list ap; 175 176 /* The only valid flag during creation is IT_SOFT. */ 177 if ((flags & ~IT_SOFT) != 0) 178 return (EINVAL); 179 180 ithd = malloc(sizeof(struct ithd), M_ITHREAD, M_WAITOK | M_ZERO); 181 ithd->it_vector = vector; 182 ithd->it_disable = disable; 183 ithd->it_enable = enable; 184 ithd->it_flags = flags; 185 TAILQ_INIT(&ithd->it_handlers); 186 mtx_init(&ithd->it_lock, "ithread", MTX_DEF); 187 mtx_lock(&ithd->it_lock); 188 189 va_start(ap, fmt); 190 vsnprintf(ithd->it_name, sizeof(ithd->it_name), fmt, ap); 191 va_end(ap); 192 193 error = kthread_create(ithread_loop, ithd, &p, RFSTOPPED | RFHIGHPID, 194 "%s", ithd->it_name); 195 if (error) { 196 mtx_destroy(&ithd->it_lock); 197 free(ithd, M_ITHREAD); 198 return (error); 199 } 200 td = &p->p_thread; /* XXXKSE */ 201 td->td_ksegrp->kg_pri.pri_class = PRI_ITHD; 202 td->td_ksegrp->kg_pri.pri_level = PRI_MAX_ITHD; 203 p->p_stat = SWAIT; 204 ithd->it_td = td; 205 td->td_ithd = ithd; 206 if (ithread != NULL) 207 *ithread = ithd; 208 mtx_unlock(&ithd->it_lock); 209 210 CTR1(KTR_INTR, __func__ ": created %s", ithd->it_name); 211 return (0); 212 } 213 214 int 215 ithread_destroy(struct ithd *ithread) 216 { 217 218 struct thread *td; 219 struct proc *p; 220 if (ithread == NULL) 221 return (EINVAL); 222 223 td = ithread->it_td; 224 p = td->td_proc; 225 mtx_lock(&ithread->it_lock); 226 if (!TAILQ_EMPTY(&ithread->it_handlers)) { 227 mtx_unlock(&ithread->it_lock); 228 return (EINVAL); 229 } 230 ithread->it_flags |= IT_DEAD; 231 mtx_lock_spin(&sched_lock); 232 if (p->p_stat == SWAIT) { 233 p->p_stat = SRUN; /* XXXKSE */ 234 setrunqueue(td); 235 } 236 mtx_unlock_spin(&sched_lock); 237 mtx_unlock(&ithread->it_lock); 238 CTR1(KTR_INTR, __func__ ": killing %s", ithread->it_name); 239 return (0); 240 } 241 242 int 243 ithread_add_handler(struct ithd* ithread, const char *name, 244 driver_intr_t handler, void *arg, u_char pri, enum intr_type flags, 245 void **cookiep) 246 { 247 struct intrhand *ih, *temp_ih; 248 249 if (ithread == NULL || name == NULL || handler == NULL) 250 return (EINVAL); 251 if ((flags & INTR_FAST) !=0) 252 flags |= INTR_EXCL; 253 254 ih = malloc(sizeof(struct intrhand), M_ITHREAD, M_WAITOK | M_ZERO); 255 ih->ih_handler = handler; 256 ih->ih_argument = arg; 257 ih->ih_name = name; 258 ih->ih_ithread = ithread; 259 ih->ih_pri = pri; 260 if (flags & INTR_FAST) 261 ih->ih_flags = IH_FAST | IH_EXCLUSIVE; 262 else if (flags & INTR_EXCL) 263 ih->ih_flags = IH_EXCLUSIVE; 264 if (flags & INTR_MPSAFE) 265 ih->ih_flags |= IH_MPSAFE; 266 if (flags & INTR_ENTROPY) 267 ih->ih_flags |= IH_ENTROPY; 268 269 mtx_lock(&ithread->it_lock); 270 if ((flags & INTR_EXCL) !=0 && !TAILQ_EMPTY(&ithread->it_handlers)) 271 goto fail; 272 if (!TAILQ_EMPTY(&ithread->it_handlers) && 273 (TAILQ_FIRST(&ithread->it_handlers)->ih_flags & IH_EXCLUSIVE) != 0) 274 goto fail; 275 276 TAILQ_FOREACH(temp_ih, &ithread->it_handlers, ih_next) 277 if (temp_ih->ih_pri > ih->ih_pri) 278 break; 279 if (temp_ih == NULL) 280 TAILQ_INSERT_TAIL(&ithread->it_handlers, ih, ih_next); 281 else 282 TAILQ_INSERT_BEFORE(temp_ih, ih, ih_next); 283 ithread_update(ithread); 284 mtx_unlock(&ithread->it_lock); 285 286 if (cookiep != NULL) 287 *cookiep = ih; 288 CTR2(KTR_INTR, __func__ ": added %s to %s", ih->ih_name, 289 ithread->it_name); 290 return (0); 291 292 fail: 293 mtx_unlock(&ithread->it_lock); 294 free(ih, M_ITHREAD); 295 return (EINVAL); 296 } 297 298 int 299 ithread_remove_handler(void *cookie) 300 { 301 struct intrhand *handler = (struct intrhand *)cookie; 302 struct ithd *ithread; 303 #ifdef INVARIANTS 304 struct intrhand *ih; 305 #endif 306 307 if (handler == NULL) 308 return (EINVAL); 309 ithread = handler->ih_ithread; 310 KASSERT(ithread != NULL, 311 ("interrupt handler \"%s\" has a NULL interrupt thread", 312 handler->ih_name)); 313 CTR2(KTR_INTR, __func__ ": removing %s from %s", handler->ih_name, 314 ithread->it_name); 315 mtx_lock(&ithread->it_lock); 316 #ifdef INVARIANTS 317 TAILQ_FOREACH(ih, &ithread->it_handlers, ih_next) 318 if (ih == handler) 319 goto ok; 320 mtx_unlock(&ithread->it_lock); 321 panic("interrupt handler \"%s\" not found in interrupt thread \"%s\"", 322 ih->ih_name, ithread->it_name); 323 ok: 324 #endif 325 /* 326 * If the interrupt thread is already running, then just mark this 327 * handler as being dead and let the ithread do the actual removal. 328 */ 329 mtx_lock_spin(&sched_lock); 330 if (ithread->it_td->td_proc->p_stat != SWAIT) { 331 handler->ih_flags |= IH_DEAD; 332 333 /* 334 * Ensure that the thread will process the handler list 335 * again and remove this handler if it has already passed 336 * it on the list. 337 */ 338 ithread->it_need = 1; 339 } else 340 TAILQ_REMOVE(&ithread->it_handlers, handler, ih_next); 341 mtx_unlock_spin(&sched_lock); 342 if ((handler->ih_flags & IH_DEAD) != 0) 343 msleep(handler, &ithread->it_lock, PUSER, "itrmh", 0); 344 ithread_update(ithread); 345 mtx_unlock(&ithread->it_lock); 346 free(handler, M_ITHREAD); 347 return (0); 348 } 349 350 int 351 ithread_schedule(struct ithd *ithread, int do_switch) 352 { 353 struct int_entropy entropy; 354 struct thread *td; 355 struct proc *p; 356 357 /* 358 * If no ithread or no handlers, then we have a stray interrupt. 359 */ 360 if ((ithread == NULL) || TAILQ_EMPTY(&ithread->it_handlers)) 361 return (EINVAL); 362 363 /* 364 * If any of the handlers for this ithread claim to be good 365 * sources of entropy, then gather some. 366 */ 367 if (harvest.interrupt && ithread->it_flags & IT_ENTROPY) { 368 entropy.vector = ithread->it_vector; 369 entropy.proc = curthread->td_proc;; 370 random_harvest(&entropy, sizeof(entropy), 2, 0, 371 RANDOM_INTERRUPT); 372 } 373 374 td = ithread->it_td; 375 p = td->td_proc; 376 KASSERT(p != NULL, ("ithread %s has no process", ithread->it_name)); 377 CTR3(KTR_INTR, __func__ ": pid %d: (%s) need = %d", p->p_pid, p->p_comm, 378 ithread->it_need); 379 380 /* 381 * Set it_need to tell the thread to keep running if it is already 382 * running. Then, grab sched_lock and see if we actually need to 383 * put this thread on the runqueue. If so and the do_switch flag is 384 * true, then switch to the ithread immediately. Otherwise, set the 385 * needresched flag to guarantee that this ithread will run before any 386 * userland processes. 387 */ 388 ithread->it_need = 1; 389 mtx_lock_spin(&sched_lock); 390 if (p->p_stat == SWAIT) { 391 CTR1(KTR_INTR, __func__ ": setrunqueue %d", p->p_pid); 392 p->p_stat = SRUN; 393 setrunqueue(td); /* XXXKSE */ 394 if (do_switch && curthread->td_proc->p_stat == SRUN) { 395 if (curthread != PCPU_GET(idlethread)) 396 setrunqueue(curthread); 397 curthread->td_proc->p_stats->p_ru.ru_nivcsw++; 398 mi_switch(); 399 } else 400 curthread->td_kse->ke_flags |= KEF_NEEDRESCHED; 401 } else { 402 CTR3(KTR_INTR, __func__ ": pid %d: it_need %d, state %d", 403 p->p_pid, ithread->it_need, p->p_stat); 404 } 405 mtx_unlock_spin(&sched_lock); 406 407 return (0); 408 } 409 410 int 411 swi_add(struct ithd **ithdp, const char *name, driver_intr_t handler, 412 void *arg, int pri, enum intr_type flags, void **cookiep) 413 { 414 struct ithd *ithd; 415 int error; 416 417 if (flags & (INTR_FAST | INTR_ENTROPY)) 418 return (EINVAL); 419 420 ithd = (ithdp != NULL) ? *ithdp : NULL; 421 422 if (ithd != NULL) { 423 if ((ithd->it_flags & IT_SOFT) == 0) 424 return(EINVAL); 425 } else { 426 error = ithread_create(&ithd, pri, IT_SOFT, NULL, NULL, 427 "swi%d:", pri); 428 if (error) 429 return (error); 430 431 if (ithdp != NULL) 432 *ithdp = ithd; 433 } 434 return (ithread_add_handler(ithd, name, handler, arg, 435 (pri * RQ_PPQ) + PI_SOFT, flags, cookiep)); 436 } 437 438 439 /* 440 * Schedule a heavyweight software interrupt process. 441 */ 442 void 443 swi_sched(void *cookie, int flags) 444 { 445 struct intrhand *ih = (struct intrhand *)cookie; 446 struct ithd *it = ih->ih_ithread; 447 int error; 448 449 atomic_add_int(&cnt.v_intr, 1); /* one more global interrupt */ 450 451 CTR3(KTR_INTR, "swi_sched pid %d(%s) need=%d", 452 it->it_td->td_proc->p_pid, it->it_td->td_proc->p_comm, it->it_need); 453 454 /* 455 * Set ih_need for this handler so that if the ithread is already 456 * running it will execute this handler on the next pass. Otherwise, 457 * it will execute it the next time it runs. 458 */ 459 atomic_store_rel_int(&ih->ih_need, 1); 460 if (!(flags & SWI_DELAY)) { 461 error = ithread_schedule(it, !cold && flags & SWI_SWITCH); 462 KASSERT(error == 0, ("stray software interrupt")); 463 } 464 } 465 466 /* 467 * This is the main code for interrupt threads. 468 */ 469 void 470 ithread_loop(void *arg) 471 { 472 struct ithd *ithd; /* our thread context */ 473 struct intrhand *ih; /* and our interrupt handler chain */ 474 struct thread *td; 475 struct proc *p; 476 477 td = curthread; 478 p = td->td_proc; 479 ithd = (struct ithd *)arg; /* point to myself */ 480 KASSERT(ithd->it_td == td && td->td_ithd == ithd, 481 (__func__ ": ithread and proc linkage out of sync")); 482 483 /* 484 * As long as we have interrupts outstanding, go through the 485 * list of handlers, giving each one a go at it. 486 */ 487 for (;;) { 488 /* 489 * If we are an orphaned thread, then just die. 490 */ 491 if (ithd->it_flags & IT_DEAD) { 492 CTR2(KTR_INTR, __func__ ": pid %d: (%s) exiting", 493 p->p_pid, p->p_comm); 494 td->td_ithd = NULL; 495 mtx_destroy(&ithd->it_lock); 496 mtx_lock(&Giant); 497 free(ithd, M_ITHREAD); 498 kthread_exit(0); 499 } 500 501 CTR3(KTR_INTR, __func__ ": pid %d: (%s) need=%d", 502 p->p_pid, p->p_comm, ithd->it_need); 503 while (ithd->it_need) { 504 /* 505 * Service interrupts. If another interrupt 506 * arrives while we are running, they will set 507 * it_need to denote that we should make 508 * another pass. 509 */ 510 atomic_store_rel_int(&ithd->it_need, 0); 511 restart: 512 TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) { 513 if (ithd->it_flags & IT_SOFT && !ih->ih_need) 514 continue; 515 atomic_store_rel_int(&ih->ih_need, 0); 516 CTR5(KTR_INTR, 517 __func__ ": pid %d ih=%p: %p(%p) flg=%x", 518 p->p_pid, (void *)ih, 519 (void *)ih->ih_handler, ih->ih_argument, 520 ih->ih_flags); 521 522 if ((ih->ih_flags & IH_DEAD) != 0) { 523 mtx_lock(&ithd->it_lock); 524 TAILQ_REMOVE(&ithd->it_handlers, ih, 525 ih_next); 526 wakeup(ih); 527 mtx_unlock(&ithd->it_lock); 528 goto restart; 529 } 530 if ((ih->ih_flags & IH_MPSAFE) == 0) 531 mtx_lock(&Giant); 532 ih->ih_handler(ih->ih_argument); 533 if ((ih->ih_flags & IH_MPSAFE) == 0) 534 mtx_unlock(&Giant); 535 } 536 } 537 538 /* 539 * Processed all our interrupts. Now get the sched 540 * lock. This may take a while and it_need may get 541 * set again, so we have to check it again. 542 */ 543 mtx_assert(&Giant, MA_NOTOWNED); 544 mtx_lock_spin(&sched_lock); 545 if (!ithd->it_need) { 546 /* 547 * Should we call this earlier in the loop above? 548 */ 549 if (ithd->it_enable != NULL) 550 ithd->it_enable(ithd->it_vector); 551 p->p_stat = SWAIT; /* we're idle */ 552 p->p_stats->p_ru.ru_nvcsw++; 553 CTR1(KTR_INTR, __func__ ": pid %d: done", p->p_pid); 554 mi_switch(); 555 CTR1(KTR_INTR, __func__ ": pid %d: resumed", p->p_pid); 556 } 557 mtx_unlock_spin(&sched_lock); 558 } 559 } 560 561 /* 562 * Start standard software interrupt threads 563 */ 564 static void 565 start_softintr(void *dummy) 566 { 567 568 if (swi_add(NULL, "net", swi_net, NULL, SWI_NET, 0, &net_ih) || 569 swi_add(&clk_ithd, "clock", softclock, NULL, SWI_CLOCK, 570 INTR_MPSAFE, &softclock_ih) || 571 swi_add(NULL, "vm", swi_vm, NULL, SWI_VM, 0, &vm_ih)) 572 panic("died while creating standard software ithreads"); 573 574 PROC_LOCK(clk_ithd->it_td->td_proc); 575 clk_ithd->it_td->td_proc->p_flag |= P_NOLOAD; 576 PROC_UNLOCK(clk_ithd->it_td->td_proc); 577 } 578 SYSINIT(start_softintr, SI_SUB_SOFTINTR, SI_ORDER_FIRST, start_softintr, NULL) 579 580 void 581 legacy_setsoftnet(void) 582 { 583 swi_sched(net_ih, SWI_NOSWITCH); 584 } 585 586 /* 587 * XXX: This should really be in the network code somewhere and installed 588 * via a SI_SUB_SOFINTR, SI_ORDER_MIDDLE sysinit. 589 */ 590 void (*netisrs[32]) __P((void)); 591 volatile unsigned int netisr; /* scheduling bits for network */ 592 593 int 594 register_netisr(num, handler) 595 int num; 596 netisr_t *handler; 597 { 598 599 if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) { 600 printf("register_netisr: bad isr number: %d\n", num); 601 return (EINVAL); 602 } 603 netisrs[num] = handler; 604 return (0); 605 } 606 607 int 608 unregister_netisr(num) 609 int num; 610 { 611 612 if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) { 613 printf("unregister_netisr: bad isr number: %d\n", num); 614 return (EINVAL); 615 } 616 netisrs[num] = NULL; 617 return (0); 618 } 619 620 static void 621 swi_net(void *dummy) 622 { 623 u_int bits; 624 int i; 625 626 bits = atomic_readandclear_int(&netisr); 627 while ((i = ffs(bits)) != 0) { 628 i--; 629 if (netisrs[i] != NULL) 630 netisrs[i](); 631 else 632 printf("swi_net: unregistered isr number: %d.\n", i); 633 bits &= ~(1 << i); 634 } 635 } 636 637 /* 638 * Sysctls used by systat and others: hw.intrnames and hw.intrcnt. 639 * The data for this machine dependent, and the declarations are in machine 640 * dependent code. The layout of intrnames and intrcnt however is machine 641 * independent. 642 * 643 * We do not know the length of intrcnt and intrnames at compile time, so 644 * calculate things at run time. 645 */ 646 static int 647 sysctl_intrnames(SYSCTL_HANDLER_ARGS) 648 { 649 return (sysctl_handle_opaque(oidp, intrnames, eintrnames - intrnames, 650 req)); 651 } 652 653 SYSCTL_PROC(_hw, OID_AUTO, intrnames, CTLTYPE_OPAQUE | CTLFLAG_RD, 654 NULL, 0, sysctl_intrnames, "", "Interrupt Names"); 655 656 static int 657 sysctl_intrcnt(SYSCTL_HANDLER_ARGS) 658 { 659 return (sysctl_handle_opaque(oidp, intrcnt, 660 (char *)eintrcnt - (char *)intrcnt, req)); 661 } 662 663 SYSCTL_PROC(_hw, OID_AUTO, intrcnt, CTLTYPE_OPAQUE | CTLFLAG_RD, 664 NULL, 0, sysctl_intrcnt, "", "Interrupt Counts"); 665