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