xref: /freebsd/sys/kern/kern_intr.c (revision f9218d3d4fd34f082473b3a021c6d4d109fb47cf)
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/conf.h>
34 #include <sys/rtprio.h>
35 #include <sys/systm.h>
36 #include <sys/interrupt.h>
37 #include <sys/kernel.h>
38 #include <sys/kthread.h>
39 #include <sys/ktr.h>
40 #include <sys/lock.h>
41 #include <sys/malloc.h>
42 #include <sys/mutex.h>
43 #include <sys/proc.h>
44 #include <sys/random.h>
45 #include <sys/resourcevar.h>
46 #include <sys/sysctl.h>
47 #include <sys/unistd.h>
48 #include <sys/vmmeter.h>
49 #include <machine/atomic.h>
50 #include <machine/cpu.h>
51 #include <machine/md_var.h>
52 #include <machine/stdarg.h>
53 
54 struct	int_entropy {
55 	struct	proc *proc;
56 	int	vector;
57 };
58 
59 void	*vm_ih;
60 void	*softclock_ih;
61 struct	ithd *clk_ithd;
62 struct	ithd *tty_ithd;
63 
64 static MALLOC_DEFINE(M_ITHREAD, "ithread", "Interrupt Threads");
65 
66 static void	ithread_update(struct ithd *);
67 static void	ithread_loop(void *);
68 static void	start_softintr(void *);
69 
70 u_char
71 ithread_priority(enum intr_type flags)
72 {
73 	u_char pri;
74 
75 	flags &= (INTR_TYPE_TTY | INTR_TYPE_BIO | INTR_TYPE_NET |
76 	    INTR_TYPE_CAM | INTR_TYPE_MISC | INTR_TYPE_CLK | INTR_TYPE_AV);
77 	switch (flags) {
78 	case INTR_TYPE_TTY:
79 		pri = PI_TTYLOW;
80 		break;
81 	case INTR_TYPE_BIO:
82 		/*
83 		 * XXX We need to refine this.  BSD/OS distinguishes
84 		 * between tape and disk priorities.
85 		 */
86 		pri = PI_DISK;
87 		break;
88 	case INTR_TYPE_NET:
89 		pri = PI_NET;
90 		break;
91 	case INTR_TYPE_CAM:
92 		pri = PI_DISK;          /* XXX or PI_CAM? */
93 		break;
94 	case INTR_TYPE_AV:		/* Audio/video */
95 		pri = PI_AV;
96 		break;
97 	case INTR_TYPE_CLK:
98 		pri = PI_REALTIME;
99 		break;
100 	case INTR_TYPE_MISC:
101 		pri = PI_DULL;          /* don't care */
102 		break;
103 	default:
104 		/* We didn't specify an interrupt level. */
105 		panic("ithread_priority: no interrupt type in flags");
106 	}
107 
108 	return pri;
109 }
110 
111 /*
112  * Regenerate the name (p_comm) and priority for a threaded interrupt thread.
113  */
114 static void
115 ithread_update(struct ithd *ithd)
116 {
117 	struct intrhand *ih;
118 	struct thread *td;
119 	struct proc *p;
120 	int entropy;
121 
122 	mtx_assert(&ithd->it_lock, MA_OWNED);
123 	td = ithd->it_td;
124 	if (td == NULL)
125 		return;
126 	p = td->td_proc;
127 
128 	strlcpy(p->p_comm, ithd->it_name, sizeof(p->p_comm));
129 
130 	ih = TAILQ_FIRST(&ithd->it_handlers);
131 	if (ih == NULL) {
132 		mtx_lock_spin(&sched_lock);
133 		td->td_priority = PRI_MAX_ITHD;
134 		td->td_base_pri = PRI_MAX_ITHD;
135 		mtx_unlock_spin(&sched_lock);
136 		ithd->it_flags &= ~IT_ENTROPY;
137 		return;
138 	}
139 	entropy = 0;
140 	mtx_lock_spin(&sched_lock);
141 	td->td_priority = ih->ih_pri;
142 	td->td_base_pri = ih->ih_pri;
143 	mtx_unlock_spin(&sched_lock);
144 	TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) {
145 		if (strlen(p->p_comm) + strlen(ih->ih_name) + 1 <
146 		    sizeof(p->p_comm)) {
147 			strcat(p->p_comm, " ");
148 			strcat(p->p_comm, ih->ih_name);
149 		} else if (strlen(p->p_comm) + 1 == sizeof(p->p_comm)) {
150 			if (p->p_comm[sizeof(p->p_comm) - 2] == '+')
151 				p->p_comm[sizeof(p->p_comm) - 2] = '*';
152 			else
153 				p->p_comm[sizeof(p->p_comm) - 2] = '+';
154 		} else
155 			strcat(p->p_comm, "+");
156 		if (ih->ih_flags & IH_ENTROPY)
157 			entropy++;
158 	}
159 	if (entropy)
160 		ithd->it_flags |= IT_ENTROPY;
161 	else
162 		ithd->it_flags &= ~IT_ENTROPY;
163 	CTR2(KTR_INTR, "%s: updated %s", __func__, 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", 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 	    0, "%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 	TD_SET_IWAIT(td);
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 
212 int
213 ithread_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 (TD_AWAITING_INTR(td)) {
231 		TD_CLR_IWAIT(td);
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 
240 int
241 ithread_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 
290 fail:
291 	mtx_unlock(&ithread->it_lock);
292 	free(ih, M_ITHREAD);
293 	return (EINVAL);
294 }
295 
296 int
297 ithread_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);
321 ok:
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 (!TD_AWAITING_INTR(ithread->it_td)) {
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 
348 int
349 ithread_schedule(struct ithd *ithread, int do_switch)
350 {
351 	struct int_entropy entropy;
352 	struct thread *td;
353 	struct thread *ctd;
354 	struct proc *p;
355 
356 	/*
357 	 * If no ithread or no handlers, then we have a stray interrupt.
358 	 */
359 	if ((ithread == NULL) || TAILQ_EMPTY(&ithread->it_handlers))
360 		return (EINVAL);
361 
362 	ctd = curthread;
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 = ctd->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 	CTR4(KTR_INTR, "%s: pid %d: (%s) need = %d",
378 	    __func__, p->p_pid, p->p_comm, 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 and it is safe to switch, then switch to the ithread
385 	 * immediately.  Otherwise, set the needresched flag to guarantee
386 	 * that this ithread will run before any userland processes.
387 	 */
388 	ithread->it_need = 1;
389 	mtx_lock_spin(&sched_lock);
390 	if (TD_AWAITING_INTR(td)) {
391 		CTR2(KTR_INTR, "%s: setrunqueue %d", __func__, p->p_pid);
392 		TD_CLR_IWAIT(td);
393 		setrunqueue(td);
394 		if (do_switch &&
395 		    (ctd->td_critnest == 1) ) {
396 			KASSERT((TD_IS_RUNNING(ctd)),
397 			    ("ithread_schedule: Bad state for curthread."));
398 			ctd->td_proc->p_stats->p_ru.ru_nivcsw++;
399 			if (ctd->td_kse->ke_flags & KEF_IDLEKSE)
400 				ctd->td_state = TDS_CAN_RUN; /* XXXKSE */
401 			mi_switch();
402 		} else {
403 			curthread->td_flags |= TDF_NEEDRESCHED;
404 		}
405 	} else {
406 		CTR4(KTR_INTR, "%s: pid %d: it_need %d, state %d",
407 		    __func__, p->p_pid, ithread->it_need, p->p_state);
408 	}
409 	mtx_unlock_spin(&sched_lock);
410 
411 	return (0);
412 }
413 
414 int
415 swi_add(struct ithd **ithdp, const char *name, driver_intr_t handler,
416 	    void *arg, int pri, enum intr_type flags, void **cookiep)
417 {
418 	struct ithd *ithd;
419 	int error;
420 
421 	if (flags & (INTR_FAST | INTR_ENTROPY))
422 		return (EINVAL);
423 
424 	ithd = (ithdp != NULL) ? *ithdp : NULL;
425 
426 	if (ithd != NULL) {
427 		if ((ithd->it_flags & IT_SOFT) == 0)
428 			return(EINVAL);
429 	} else {
430 		error = ithread_create(&ithd, pri, IT_SOFT, NULL, NULL,
431 		    "swi%d:", pri);
432 		if (error)
433 			return (error);
434 
435 		if (ithdp != NULL)
436 			*ithdp = ithd;
437 	}
438 	return (ithread_add_handler(ithd, name, handler, arg,
439 		    (pri * RQ_PPQ) + PI_SOFT, flags, cookiep));
440 }
441 
442 
443 /*
444  * Schedule a heavyweight software interrupt process.
445  */
446 void
447 swi_sched(void *cookie, int flags)
448 {
449 	struct intrhand *ih = (struct intrhand *)cookie;
450 	struct ithd *it = ih->ih_ithread;
451 	int error;
452 
453 	atomic_add_int(&cnt.v_intr, 1); /* one more global interrupt */
454 
455 	CTR3(KTR_INTR, "swi_sched pid %d(%s) need=%d",
456 		it->it_td->td_proc->p_pid, it->it_td->td_proc->p_comm, it->it_need);
457 
458 	/*
459 	 * Set ih_need for this handler so that if the ithread is already
460 	 * running it will execute this handler on the next pass.  Otherwise,
461 	 * it will execute it the next time it runs.
462 	 */
463 	atomic_store_rel_int(&ih->ih_need, 1);
464 	if (!(flags & SWI_DELAY)) {
465 		error = ithread_schedule(it, !cold && !dumping);
466 		KASSERT(error == 0, ("stray software interrupt"));
467 	}
468 }
469 
470 /*
471  * This is the main code for interrupt threads.
472  */
473 static void
474 ithread_loop(void *arg)
475 {
476 	struct ithd *ithd;		/* our thread context */
477 	struct intrhand *ih;		/* and our interrupt handler chain */
478 	struct thread *td;
479 	struct proc *p;
480 
481 	td = curthread;
482 	p = td->td_proc;
483 	ithd = (struct ithd *)arg;	/* point to myself */
484 	KASSERT(ithd->it_td == td && td->td_ithd == ithd,
485 	    ("%s: ithread and proc linkage out of sync", __func__));
486 
487 	/*
488 	 * As long as we have interrupts outstanding, go through the
489 	 * list of handlers, giving each one a go at it.
490 	 */
491 	for (;;) {
492 		/*
493 		 * If we are an orphaned thread, then just die.
494 		 */
495 		if (ithd->it_flags & IT_DEAD) {
496 			CTR3(KTR_INTR, "%s: pid %d: (%s) exiting", __func__,
497 			    p->p_pid, p->p_comm);
498 			td->td_ithd = NULL;
499 			mtx_destroy(&ithd->it_lock);
500 			mtx_lock(&Giant);
501 			free(ithd, M_ITHREAD);
502 			kthread_exit(0);
503 		}
504 
505 		CTR4(KTR_INTR, "%s: pid %d: (%s) need=%d", __func__,
506 		     p->p_pid, p->p_comm, ithd->it_need);
507 		while (ithd->it_need) {
508 			/*
509 			 * Service interrupts.  If another interrupt
510 			 * arrives while we are running, they will set
511 			 * it_need to denote that we should make
512 			 * another pass.
513 			 */
514 			atomic_store_rel_int(&ithd->it_need, 0);
515 restart:
516 			TAILQ_FOREACH(ih, &ithd->it_handlers, ih_next) {
517 				if (ithd->it_flags & IT_SOFT && !ih->ih_need)
518 					continue;
519 				atomic_store_rel_int(&ih->ih_need, 0);
520 				CTR6(KTR_INTR,
521 				    "%s: pid %d ih=%p: %p(%p) flg=%x", __func__,
522 				    p->p_pid, (void *)ih,
523 				    (void *)ih->ih_handler, ih->ih_argument,
524 				    ih->ih_flags);
525 
526 				if ((ih->ih_flags & IH_DEAD) != 0) {
527 					mtx_lock(&ithd->it_lock);
528 					TAILQ_REMOVE(&ithd->it_handlers, ih,
529 					    ih_next);
530 					wakeup(ih);
531 					mtx_unlock(&ithd->it_lock);
532 					goto restart;
533 				}
534 				if ((ih->ih_flags & IH_MPSAFE) == 0)
535 					mtx_lock(&Giant);
536 				ih->ih_handler(ih->ih_argument);
537 				if ((ih->ih_flags & IH_MPSAFE) == 0)
538 					mtx_unlock(&Giant);
539 			}
540 		}
541 
542 		/*
543 		 * Processed all our interrupts.  Now get the sched
544 		 * lock.  This may take a while and it_need may get
545 		 * set again, so we have to check it again.
546 		 */
547 		mtx_assert(&Giant, MA_NOTOWNED);
548 		mtx_lock_spin(&sched_lock);
549 		if (!ithd->it_need) {
550 			/*
551 			 * Should we call this earlier in the loop above?
552 			 */
553 			if (ithd->it_enable != NULL)
554 				ithd->it_enable(ithd->it_vector);
555 			TD_SET_IWAIT(td); /* we're idle */
556 			p->p_stats->p_ru.ru_nvcsw++;
557 			CTR2(KTR_INTR, "%s: pid %d: done", __func__, p->p_pid);
558 			mi_switch();
559 			CTR2(KTR_INTR, "%s: pid %d: resumed", __func__, p->p_pid);
560 		}
561 		mtx_unlock_spin(&sched_lock);
562 	}
563 }
564 
565 /*
566  * Start standard software interrupt threads
567  */
568 static void
569 start_softintr(void *dummy)
570 {
571 
572 	if (swi_add(&clk_ithd, "clock", softclock, NULL, SWI_CLOCK,
573 		INTR_MPSAFE, &softclock_ih) ||
574 	    swi_add(NULL, "vm", swi_vm, NULL, SWI_VM, 0, &vm_ih))
575 		panic("died while creating standard software ithreads");
576 
577 	PROC_LOCK(clk_ithd->it_td->td_proc);
578 	clk_ithd->it_td->td_proc->p_flag |= P_NOLOAD;
579 	PROC_UNLOCK(clk_ithd->it_td->td_proc);
580 }
581 SYSINIT(start_softintr, SI_SUB_SOFTINTR, SI_ORDER_FIRST, start_softintr, NULL)
582 
583 /*
584  * Sysctls used by systat and others: hw.intrnames and hw.intrcnt.
585  * The data for this machine dependent, and the declarations are in machine
586  * dependent code.  The layout of intrnames and intrcnt however is machine
587  * independent.
588  *
589  * We do not know the length of intrcnt and intrnames at compile time, so
590  * calculate things at run time.
591  */
592 static int
593 sysctl_intrnames(SYSCTL_HANDLER_ARGS)
594 {
595 	return (sysctl_handle_opaque(oidp, intrnames, eintrnames - intrnames,
596 	   req));
597 }
598 
599 SYSCTL_PROC(_hw, OID_AUTO, intrnames, CTLTYPE_OPAQUE | CTLFLAG_RD,
600     NULL, 0, sysctl_intrnames, "", "Interrupt Names");
601 
602 static int
603 sysctl_intrcnt(SYSCTL_HANDLER_ARGS)
604 {
605 	return (sysctl_handle_opaque(oidp, intrcnt,
606 	    (char *)eintrcnt - (char *)intrcnt, req));
607 }
608 
609 SYSCTL_PROC(_hw, OID_AUTO, intrcnt, CTLTYPE_OPAQUE | CTLFLAG_RD,
610     NULL, 0, sysctl_intrcnt, "", "Interrupt Counts");
611