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