kern_intr.c revision 94288
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: head/sys/kern/kern_intr.c 94288 2002-04-09 16:26:37Z jhb $ 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 55struct int_entropy { 56 struct proc *proc; 57 int vector; 58}; 59 60void *net_ih; 61void *vm_ih; 62void *softclock_ih; 63struct ithd *clk_ithd; 64struct ithd *tty_ithd; 65 66static MALLOC_DEFINE(M_ITHREAD, "ithread", "Interrupt Threads"); 67 68static void ithread_update(struct ithd *); 69static void ithread_loop(void *); 70static void start_softintr(void *); 71static void swi_net(void *); 72 73u_char 74ithread_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 */ 117static void 118ithread_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_priority = PRI_MAX_ITHD; 135 ithd->it_flags &= ~IT_ENTROPY; 136 return; 137 } 138 139 entropy = 0; 140 td->td_priority = ih->ih_pri; 141 td->td_base_pri = 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 CTR2(KTR_INTR, "%s: updated %s\n", __func__, p->p_comm); 164} 165 166int 167ithread_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", NULL, MTX_DEF); 187 188 va_start(ap, fmt); 189 vsnprintf(ithd->it_name, sizeof(ithd->it_name), fmt, ap); 190 va_end(ap); 191 192 error = kthread_create(ithread_loop, ithd, &p, RFSTOPPED | RFHIGHPID, 193 "%s", ithd->it_name); 194 if (error) { 195 mtx_destroy(&ithd->it_lock); 196 free(ithd, M_ITHREAD); 197 return (error); 198 } 199 td = FIRST_THREAD_IN_PROC(p); /* XXXKSE */ 200 td->td_ksegrp->kg_pri_class = PRI_ITHD; 201 td->td_priority = PRI_MAX_ITHD; 202 p->p_stat = SWAIT; 203 ithd->it_td = td; 204 td->td_ithd = ithd; 205 if (ithread != NULL) 206 *ithread = ithd; 207 208 CTR2(KTR_INTR, "%s: created %s", __func__, ithd->it_name); 209 return (0); 210} 211 212int 213ithread_destroy(struct ithd *ithread) 214{ 215 216 struct thread *td; 217 struct proc *p; 218 if (ithread == NULL) 219 return (EINVAL); 220 221 td = ithread->it_td; 222 p = td->td_proc; 223 mtx_lock(&ithread->it_lock); 224 if (!TAILQ_EMPTY(&ithread->it_handlers)) { 225 mtx_unlock(&ithread->it_lock); 226 return (EINVAL); 227 } 228 ithread->it_flags |= IT_DEAD; 229 mtx_lock_spin(&sched_lock); 230 if (p->p_stat == SWAIT) { 231 p->p_stat = SRUN; /* XXXKSE */ 232 setrunqueue(td); 233 } 234 mtx_unlock_spin(&sched_lock); 235 mtx_unlock(&ithread->it_lock); 236 CTR2(KTR_INTR, "%s: killing %s", __func__, ithread->it_name); 237 return (0); 238} 239 240int 241ithread_add_handler(struct ithd* ithread, const char *name, 242 driver_intr_t handler, void *arg, u_char pri, enum intr_type flags, 243 void **cookiep) 244{ 245 struct intrhand *ih, *temp_ih; 246 247 if (ithread == NULL || name == NULL || handler == NULL) 248 return (EINVAL); 249 if ((flags & INTR_FAST) !=0) 250 flags |= INTR_EXCL; 251 252 ih = malloc(sizeof(struct intrhand), M_ITHREAD, M_WAITOK | M_ZERO); 253 ih->ih_handler = handler; 254 ih->ih_argument = arg; 255 ih->ih_name = name; 256 ih->ih_ithread = ithread; 257 ih->ih_pri = pri; 258 if (flags & INTR_FAST) 259 ih->ih_flags = IH_FAST | IH_EXCLUSIVE; 260 else if (flags & INTR_EXCL) 261 ih->ih_flags = IH_EXCLUSIVE; 262 if (flags & INTR_MPSAFE) 263 ih->ih_flags |= IH_MPSAFE; 264 if (flags & INTR_ENTROPY) 265 ih->ih_flags |= IH_ENTROPY; 266 267 mtx_lock(&ithread->it_lock); 268 if ((flags & INTR_EXCL) !=0 && !TAILQ_EMPTY(&ithread->it_handlers)) 269 goto fail; 270 if (!TAILQ_EMPTY(&ithread->it_handlers) && 271 (TAILQ_FIRST(&ithread->it_handlers)->ih_flags & IH_EXCLUSIVE) != 0) 272 goto fail; 273 274 TAILQ_FOREACH(temp_ih, &ithread->it_handlers, ih_next) 275 if (temp_ih->ih_pri > ih->ih_pri) 276 break; 277 if (temp_ih == NULL) 278 TAILQ_INSERT_TAIL(&ithread->it_handlers, ih, ih_next); 279 else 280 TAILQ_INSERT_BEFORE(temp_ih, ih, ih_next); 281 ithread_update(ithread); 282 mtx_unlock(&ithread->it_lock); 283 284 if (cookiep != NULL) 285 *cookiep = ih; 286 CTR3(KTR_INTR, "%s: added %s to %s", __func__, ih->ih_name, 287 ithread->it_name); 288 return (0); 289 290fail: 291 mtx_unlock(&ithread->it_lock); 292 free(ih, M_ITHREAD); 293 return (EINVAL); 294} 295 296int 297ithread_remove_handler(void *cookie) 298{ 299 struct intrhand *handler = (struct intrhand *)cookie; 300 struct ithd *ithread; 301#ifdef INVARIANTS 302 struct intrhand *ih; 303#endif 304 305 if (handler == NULL) 306 return (EINVAL); 307 ithread = handler->ih_ithread; 308 KASSERT(ithread != NULL, 309 ("interrupt handler \"%s\" has a NULL interrupt thread", 310 handler->ih_name)); 311 CTR3(KTR_INTR, "%s: removing %s from %s", __func__, handler->ih_name, 312 ithread->it_name); 313 mtx_lock(&ithread->it_lock); 314#ifdef INVARIANTS 315 TAILQ_FOREACH(ih, &ithread->it_handlers, ih_next) 316 if (ih == handler) 317 goto ok; 318 mtx_unlock(&ithread->it_lock); 319 panic("interrupt handler \"%s\" not found in interrupt thread \"%s\"", 320 ih->ih_name, ithread->it_name); 321ok: 322#endif 323 /* 324 * If the interrupt thread is already running, then just mark this 325 * handler as being dead and let the ithread do the actual removal. 326 */ 327 mtx_lock_spin(&sched_lock); 328 if (ithread->it_td->td_proc->p_stat != SWAIT) { 329 handler->ih_flags |= IH_DEAD; 330 331 /* 332 * Ensure that the thread will process the handler list 333 * again and remove this handler if it has already passed 334 * it on the list. 335 */ 336 ithread->it_need = 1; 337 } else 338 TAILQ_REMOVE(&ithread->it_handlers, handler, ih_next); 339 mtx_unlock_spin(&sched_lock); 340 if ((handler->ih_flags & IH_DEAD) != 0) 341 msleep(handler, &ithread->it_lock, PUSER, "itrmh", 0); 342 ithread_update(ithread); 343 mtx_unlock(&ithread->it_lock); 344 free(handler, M_ITHREAD); 345 return (0); 346} 347 348int 349ithread_schedule(struct ithd *ithread, int do_switch) 350{ 351 struct int_entropy entropy; 352 struct thread *td; 353 struct proc *p; 354 355 /* 356 * If no ithread or no handlers, then we have a stray interrupt. 357 */ 358 if ((ithread == NULL) || TAILQ_EMPTY(&ithread->it_handlers)) 359 return (EINVAL); 360 361 /* 362 * If any of the handlers for this ithread claim to be good 363 * sources of entropy, then gather some. 364 */ 365 if (harvest.interrupt && ithread->it_flags & IT_ENTROPY) { 366 entropy.vector = ithread->it_vector; 367 entropy.proc = curthread->td_proc;; 368 random_harvest(&entropy, sizeof(entropy), 2, 0, 369 RANDOM_INTERRUPT); 370 } 371 372 td = ithread->it_td; 373 p = td->td_proc; 374 KASSERT(p != NULL, ("ithread %s has no process", ithread->it_name)); 375 CTR4(KTR_INTR, "%s: pid %d: (%s) need = %d", __func__, p->p_pid, p->p_comm, 376 ithread->it_need); 377 378 /* 379 * Set it_need to tell the thread to keep running if it is already 380 * running. Then, grab sched_lock and see if we actually need to 381 * put this thread on the runqueue. If so and the do_switch flag is 382 * true and it is safe to switch, then switch to the ithread 383 * immediately. Otherwise, set the needresched flag to guarantee 384 * that this ithread will run before any userland processes. 385 */ 386 ithread->it_need = 1; 387 mtx_lock_spin(&sched_lock); 388 if (p->p_stat == SWAIT) { 389 CTR2(KTR_INTR, "%s: setrunqueue %d", __func__, p->p_pid); 390 p->p_stat = SRUN; 391 setrunqueue(td); /* XXXKSE */ 392 if (do_switch && curthread->td_critnest == 1 && 393 curthread->td_proc->p_stat == SRUN) { 394 if (curthread != PCPU_GET(idlethread)) 395 setrunqueue(curthread); 396 curthread->td_proc->p_stats->p_ru.ru_nivcsw++; 397 mi_switch(); 398 } else 399 curthread->td_kse->ke_flags |= KEF_NEEDRESCHED; 400 } else { 401 CTR4(KTR_INTR, "%s: pid %d: it_need %d, state %d", 402 __func__, p->p_pid, ithread->it_need, p->p_stat); 403 } 404 mtx_unlock_spin(&sched_lock); 405 406 return (0); 407} 408 409int 410swi_add(struct ithd **ithdp, const char *name, driver_intr_t handler, 411 void *arg, int pri, enum intr_type flags, void **cookiep) 412{ 413 struct ithd *ithd; 414 int error; 415 416 if (flags & (INTR_FAST | INTR_ENTROPY)) 417 return (EINVAL); 418 419 ithd = (ithdp != NULL) ? *ithdp : NULL; 420 421 if (ithd != NULL) { 422 if ((ithd->it_flags & IT_SOFT) == 0) 423 return(EINVAL); 424 } else { 425 error = ithread_create(&ithd, pri, IT_SOFT, NULL, NULL, 426 "swi%d:", pri); 427 if (error) 428 return (error); 429 430 if (ithdp != NULL) 431 *ithdp = ithd; 432 } 433 return (ithread_add_handler(ithd, name, handler, arg, 434 (pri * RQ_PPQ) + PI_SOFT, flags, cookiep)); 435} 436 437 438/* 439 * Schedule a heavyweight software interrupt process. 440 */ 441void 442swi_sched(void *cookie, int flags) 443{ 444 struct intrhand *ih = (struct intrhand *)cookie; 445 struct ithd *it = ih->ih_ithread; 446 int error; 447 448 atomic_add_int(&cnt.v_intr, 1); /* one more global interrupt */ 449 450 CTR3(KTR_INTR, "swi_sched pid %d(%s) need=%d", 451 it->it_td->td_proc->p_pid, it->it_td->td_proc->p_comm, it->it_need); 452 453 /* 454 * Set ih_need for this handler so that if the ithread is already 455 * running it will execute this handler on the next pass. Otherwise, 456 * it will execute it the next time it runs. 457 */ 458 atomic_store_rel_int(&ih->ih_need, 1); 459 if (!(flags & SWI_DELAY)) { 460 error = ithread_schedule(it, !cold); 461 KASSERT(error == 0, ("stray software interrupt")); 462 } 463} 464 465/* 466 * This is the main code for interrupt threads. 467 */ 468void 469ithread_loop(void *arg) 470{ 471 struct ithd *ithd; /* our thread context */ 472 struct intrhand *ih; /* and our interrupt handler chain */ 473 struct thread *td; 474 struct proc *p; 475 476 td = curthread; 477 p = td->td_proc; 478 ithd = (struct ithd *)arg; /* point to myself */ 479 KASSERT(ithd->it_td == td && td->td_ithd == ithd, 480 ("%s: ithread and proc linkage out of sync", __func__)); 481 482 /* 483 * As long as we have interrupts outstanding, go through the 484 * list of handlers, giving each one a go at it. 485 */ 486 for (;;) { 487 /* 488 * If we are an orphaned thread, then just die. 489 */ 490 if (ithd->it_flags & IT_DEAD) { 491 CTR3(KTR_INTR, "%s: pid %d: (%s) exiting", __func__, 492 p->p_pid, p->p_comm); 493 td->td_ithd = NULL; 494 mtx_destroy(&ithd->it_lock); 495 mtx_lock(&Giant); 496 free(ithd, M_ITHREAD); 497 kthread_exit(0); 498 } 499 500 CTR4(KTR_INTR, "%s: pid %d: (%s) need=%d", __func__, 501 p->p_pid, p->p_comm, ithd->it_need); 502 while (ithd->it_need) { 503 /* 504 * Service interrupts. If another interrupt 505 * arrives while we are running, they will set 506 * it_need to denote that we should make 507 * another pass. 508 */ 509 atomic_store_rel_int(&ithd->it_need, 0); 510restart: 511 TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) { 512 if (ithd->it_flags & IT_SOFT && !ih->ih_need) 513 continue; 514 atomic_store_rel_int(&ih->ih_need, 0); 515 CTR6(KTR_INTR, 516 "%s: pid %d ih=%p: %p(%p) flg=%x", __func__, 517 p->p_pid, (void *)ih, 518 (void *)ih->ih_handler, ih->ih_argument, 519 ih->ih_flags); 520 521 if ((ih->ih_flags & IH_DEAD) != 0) { 522 mtx_lock(&ithd->it_lock); 523 TAILQ_REMOVE(&ithd->it_handlers, ih, 524 ih_next); 525 wakeup(ih); 526 mtx_unlock(&ithd->it_lock); 527 goto restart; 528 } 529 if ((ih->ih_flags & IH_MPSAFE) == 0) 530 mtx_lock(&Giant); 531 ih->ih_handler(ih->ih_argument); 532 if ((ih->ih_flags & IH_MPSAFE) == 0) 533 mtx_unlock(&Giant); 534 } 535 } 536 537 /* 538 * Processed all our interrupts. Now get the sched 539 * lock. This may take a while and it_need may get 540 * set again, so we have to check it again. 541 */ 542 mtx_assert(&Giant, MA_NOTOWNED); 543 mtx_lock_spin(&sched_lock); 544 if (!ithd->it_need) { 545 /* 546 * Should we call this earlier in the loop above? 547 */ 548 if (ithd->it_enable != NULL) 549 ithd->it_enable(ithd->it_vector); 550 p->p_stat = SWAIT; /* we're idle */ 551 p->p_stats->p_ru.ru_nvcsw++; 552 CTR2(KTR_INTR, "%s: pid %d: done", __func__, p->p_pid); 553 mi_switch(); 554 CTR2(KTR_INTR, "%s: pid %d: resumed", __func__, p->p_pid); 555 } 556 mtx_unlock_spin(&sched_lock); 557 } 558} 559 560/* 561 * Start standard software interrupt threads 562 */ 563static void 564start_softintr(void *dummy) 565{ 566 567 if (swi_add(NULL, "net", swi_net, NULL, SWI_NET, 0, &net_ih) || 568 swi_add(&clk_ithd, "clock", softclock, NULL, SWI_CLOCK, 569 INTR_MPSAFE, &softclock_ih) || 570 swi_add(NULL, "vm", swi_vm, NULL, SWI_VM, 0, &vm_ih)) 571 panic("died while creating standard software ithreads"); 572 573 PROC_LOCK(clk_ithd->it_td->td_proc); 574 clk_ithd->it_td->td_proc->p_flag |= P_NOLOAD; 575 PROC_UNLOCK(clk_ithd->it_td->td_proc); 576} 577SYSINIT(start_softintr, SI_SUB_SOFTINTR, SI_ORDER_FIRST, start_softintr, NULL) 578 579void 580legacy_setsoftnet(void) 581{ 582 swi_sched(net_ih, 0); 583} 584 585/* 586 * XXX: This should really be in the network code somewhere and installed 587 * via a SI_SUB_SOFINTR, SI_ORDER_MIDDLE sysinit. 588 */ 589void (*netisrs[32])(void); 590volatile unsigned int netisr; /* scheduling bits for network */ 591 592int 593register_netisr(num, handler) 594 int num; 595 netisr_t *handler; 596{ 597 598 if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) { 599 printf("register_netisr: bad isr number: %d\n", num); 600 return (EINVAL); 601 } 602 netisrs[num] = handler; 603 return (0); 604} 605 606int 607unregister_netisr(num) 608 int num; 609{ 610 611 if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) { 612 printf("unregister_netisr: bad isr number: %d\n", num); 613 return (EINVAL); 614 } 615 netisrs[num] = NULL; 616 return (0); 617} 618 619#ifdef DEVICE_POLLING 620 void netisr_pollmore(void); 621#endif 622 623static void 624swi_net(void *dummy) 625{ 626 u_int bits; 627 int i; 628 629#ifdef DEVICE_POLLING 630 for (;;) { 631 int pollmore; 632#endif 633 bits = atomic_readandclear_int(&netisr); 634#ifdef DEVICE_POLLING 635 if (bits == 0) 636 return; 637 pollmore = bits & (1 << NETISR_POLL); 638#endif 639 while ((i = ffs(bits)) != 0) { 640 i--; 641 if (netisrs[i] != NULL) 642 netisrs[i](); 643 else 644 printf("swi_net: unregistered isr number: %d.\n", i); 645 bits &= ~(1 << i); 646 } 647#ifdef DEVICE_POLLING 648 if (pollmore) 649 netisr_pollmore(); 650 } 651#endif 652} 653 654/* 655 * Sysctls used by systat and others: hw.intrnames and hw.intrcnt. 656 * The data for this machine dependent, and the declarations are in machine 657 * dependent code. The layout of intrnames and intrcnt however is machine 658 * independent. 659 * 660 * We do not know the length of intrcnt and intrnames at compile time, so 661 * calculate things at run time. 662 */ 663static int 664sysctl_intrnames(SYSCTL_HANDLER_ARGS) 665{ 666 return (sysctl_handle_opaque(oidp, intrnames, eintrnames - intrnames, 667 req)); 668} 669 670SYSCTL_PROC(_hw, OID_AUTO, intrnames, CTLTYPE_OPAQUE | CTLFLAG_RD, 671 NULL, 0, sysctl_intrnames, "", "Interrupt Names"); 672 673static int 674sysctl_intrcnt(SYSCTL_HANDLER_ARGS) 675{ 676 return (sysctl_handle_opaque(oidp, intrcnt, 677 (char *)eintrcnt - (char *)intrcnt, req)); 678} 679 680SYSCTL_PROC(_hw, OID_AUTO, intrcnt, CTLTYPE_OPAQUE | CTLFLAG_RD, 681 NULL, 0, sysctl_intrcnt, "", "Interrupt Counts"); 682