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