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