xref: /freebsd/sys/kern/kern_intr.c (revision 0640d357f29fb1c0daaaffadd0416c5981413afd)
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  * $Id: kern_intr.c,v 1.19 1998/08/11 15:08:13 bde Exp $
27  *
28  */
29 
30 
31 #include <sys/types.h>
32 #include <sys/malloc.h>
33 #include <sys/systm.h>
34 #include <sys/errno.h>
35 #ifdef RESOURCE_CHECK
36 #include <sys/drvresource.h>
37 #endif /* RESOURCE_CHECK */
38 
39 #include <machine/ipl.h>
40 
41 #ifdef __i386__
42 #include <i386/isa/icu.h>
43 #include <i386/isa/intr_machdep.h>
44 #endif
45 
46 #include <sys/interrupt.h>
47 
48 #include <stddef.h>
49 
50 #ifdef __i386__
51 
52 typedef struct intrec {
53 	intrmask_t	mask;
54 	inthand2_t	*handler;
55 	void		*argument;
56 	struct intrec	*next;
57 	void		*devdata;
58 	int		intr;
59 	intrmask_t	*maskptr;
60 	int		flags;
61 } intrec;
62 
63 static intrec *intreclist_head[NHWI];
64 
65 #endif
66 
67 struct swilist {
68 	swihand_t	*sl_handler;
69 	struct swilist	*sl_next;
70 };
71 
72 static struct swilist swilists[NSWI];
73 
74 #ifdef __i386__
75 
76 /*
77  * The interrupt multiplexer calls each of the handlers in turn,
78  * and applies the associated interrupt mask to "cpl", which is
79  * defined as a ".long" in /sys/i386/isa/ipl.s
80  */
81 
82 #ifndef SMP
83 static __inline intrmask_t
84 splq(intrmask_t mask)
85 {
86 	intrmask_t tmp = cpl;
87 	cpl |= mask;
88 	return (tmp);
89 }
90 #endif /* SMP */
91 
92 static void
93 intr_mux(void *arg)
94 {
95 	intrec *p = arg;
96 
97 	while (p != NULL) {
98 		int oldspl = splq(p->mask);
99 		p->handler(p->argument);
100 		splx(oldspl);
101 		p = p->next;
102 	}
103 }
104 
105 static intrec*
106 find_idesc(unsigned *maskptr, int irq)
107 {
108 	intrec *p = intreclist_head[irq];
109 
110 	while (p && p->maskptr != maskptr)
111 		p = p->next;
112 
113 	return (p);
114 }
115 
116 static intrec**
117 find_pred(intrec *idesc, int irq)
118 {
119 	intrec **pp = &intreclist_head[irq];
120 	intrec *p = *pp;
121 
122 	while (p != idesc) {
123 		if (p == NULL)
124 			return (NULL);
125 		pp = &p->next;
126 		p = *pp;
127 	}
128 	return (pp);
129 }
130 
131 /*
132  * Both the low level handler and the shared interrupt multiplexer
133  * block out further interrupts as set in the handlers "mask", while
134  * the handler is running. In fact *maskptr should be used for this
135  * purpose, but since this requires one more pointer dereference on
136  * each interrupt, we rather bother update "mask" whenever *maskptr
137  * changes. The function "update_masks" should be called **after**
138  * all manipulation of the linked list of interrupt handlers hung
139  * off of intrdec_head[irq] is complete, since the chain of handlers
140  * will both determine the *maskptr values and the instances of mask
141  * that are fixed. This function should be called with the irq for
142  * which a new handler has been add blocked, since the masks may not
143  * yet know about the use of this irq for a device of a certain class.
144  */
145 
146 static void
147 update_mux_masks(void)
148 {
149 	int irq;
150 	for (irq = 0; irq < ICU_LEN; irq++) {
151 		intrec *idesc = intreclist_head[irq];
152 		while (idesc != NULL) {
153 			if (idesc->maskptr != NULL) {
154 				/* our copy of *maskptr may be stale, refresh */
155 				idesc->mask = *idesc->maskptr;
156 			}
157 			idesc = idesc->next;
158 		}
159 	}
160 }
161 
162 static void
163 update_masks(intrmask_t *maskptr, int irq)
164 {
165 	intrmask_t mask = 1 << irq;
166 
167 	if (maskptr == NULL)
168 		return;
169 
170 	if (find_idesc(maskptr, irq) == NULL) {
171 		/* no reference to this maskptr was found in this irq's chain */
172 		if ((*maskptr & mask) == 0)
173 			return;
174 		/* the irq was included in the classes mask, remove it */
175 		INTRUNMASK(*maskptr, mask);
176 	} else {
177 		/* a reference to this maskptr was found in this irq's chain */
178 		if ((*maskptr & mask) != 0)
179 			return;
180 		/* put the irq into the classes mask */
181 		INTRMASK(*maskptr, mask);
182 	}
183 	/* we need to update all values in the intr_mask[irq] array */
184 	update_intr_masks();
185 	/* update mask in chains of the interrupt multiplex handler as well */
186 	update_mux_masks();
187 }
188 
189 /*
190  * Add interrupt handler to linked list hung off of intreclist_head[irq]
191  * and install shared interrupt multiplex handler, if necessary
192  */
193 
194 static int
195 add_intrdesc(intrec *idesc)
196 {
197 	int irq = idesc->intr;
198 
199 	intrec *head = intreclist_head[irq];
200 
201 	if (head == NULL) {
202 		/* first handler for this irq, just install it */
203 		if (icu_setup(irq, idesc->handler, idesc->argument,
204 			      idesc->maskptr, idesc->flags) != 0)
205 			return (-1);
206 
207 		update_intrname(irq, (intptr_t)idesc->devdata);
208 		/* keep reference */
209 		intreclist_head[irq] = idesc;
210 	} else {
211 		if ((idesc->flags & INTR_EXCL) != 0
212 		    || (head->flags & INTR_EXCL) != 0) {
213 			/*
214 			 * can't append new handler, if either list head or
215 			 * new handler do not allow interrupts to be shared
216 			 */
217 			if (bootverbose)
218 				printf("\tdevice combination doesn't support "
219 				       "shared irq%d\n", irq);
220 			return (-1);
221 		}
222 		if (head->next == NULL) {
223 			/*
224 			 * second handler for this irq, replace device driver's
225 			 * handler by shared interrupt multiplexer function
226 			 */
227 			icu_unset(irq, head->handler);
228 			if (icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0) != 0)
229 				return (-1);
230 			if (bootverbose)
231 				printf("\tusing shared irq%d.\n", irq);
232 			update_intrname(irq, -1);
233 		}
234 		/* just append to the end of the chain */
235 		while (head->next != NULL)
236 			head = head->next;
237 		head->next = idesc;
238 	}
239 	update_masks(idesc->maskptr, irq);
240 	return (0);
241 }
242 
243 /*
244  * Add the interrupt handler descriptor data structure created by an
245  * earlier call of create_intr() to the linked list for its irq and
246  * adjust the interrupt masks if necessary.
247  *
248  * This function effectively activates the handler.
249  */
250 
251 int
252 intr_connect(intrec *idesc)
253 {
254 	int errcode = -1;
255 	int irq;
256 
257 #ifdef RESOURCE_CHECK
258 	int resflag;
259 #endif /* RESOURCE_CHECK */
260 
261 	if (idesc == NULL)
262 		return (-1);
263 
264 	irq = idesc->intr;
265 #ifdef RESOURCE_CHECK
266 	resflag = (idesc->flags & INTR_EXCL) ? RESF_NONE : RESF_SHARED;
267 	if (resource_claim(idesc->devdata, REST_INT, resflag, irq, irq) == 0)
268 #endif /* RESOURCE_CHECK */
269 	{
270 		/* block this irq */
271 		intrmask_t oldspl = splq(1 << irq);
272 
273 		/* add irq to class selected by maskptr */
274 		errcode = add_intrdesc(idesc);
275 		splx(oldspl);
276 	}
277 	if (errcode != 0 && bootverbose)
278 		printf("\tintr_connect(irq%d) failed, result=%d\n",
279 		       irq, errcode);
280 
281 	return (errcode);
282 }
283 
284 /*
285  * Remove the interrupt handler descriptor data connected created by an
286  * earlier call of intr_connect() from the linked list and adjust the
287  * interrupt masks if necessary.
288  *
289  * This function deactivates the handler.
290  */
291 
292 int
293 intr_disconnect(intrec *idesc)
294 {
295 	intrec **hook, *head;
296 	int irq;
297 	int errcode = 0;
298 
299 	if (idesc == NULL)
300 		return (-1);
301 
302 	irq = idesc->intr;
303 
304 	/* find pointer that keeps the reference to this interrupt descriptor */
305 	hook = find_pred(idesc, irq);
306 	if (hook == NULL)
307 		return (-1);
308 
309 	/* make copy of original list head, the line after may overwrite it */
310 	head = intreclist_head[irq];
311 
312 	/* unlink: make predecessor point to idesc->next instead of to idesc */
313 	*hook = idesc->next;
314 
315 	/* now check whether the element we removed was the list head */
316 	if (idesc == head) {
317 		intrmask_t oldspl = splq(1 << irq);
318 
319 		/* we want to remove the list head, which was known to intr_mux */
320 		icu_unset(irq, (inthand2_t*)intr_mux);
321 
322 		/* check whether the new list head is the only element on list */
323 		head = intreclist_head[irq];
324 		if (head != NULL) {
325 			if (head->next != NULL) {
326 				/* install the multiplex handler with new list head as argument */
327 				errcode = icu_setup(irq, (inthand2_t*)intr_mux, head, 0, 0);
328 				if (errcode == 0)
329 					update_intrname(irq, -1);
330 			} else {
331 				/* install the one remaining handler for this irq */
332 				errcode = icu_setup(irq, head->handler,
333 						    head->argument,
334 						    head->maskptr, head->flags);
335 				if (errcode == 0)
336 					update_intrname(irq, (intptr_t)head->devdata);
337 			}
338 		}
339 		splx(oldspl);
340 	}
341 	update_masks(idesc->maskptr, irq);
342 #ifdef RESOURCE_CHECK
343 	resource_free(idesc->devdata);
344 #endif /* RESOURCE_CHECK */
345 	return (0);
346 }
347 
348 /*
349  * Create an interrupt handler descriptor data structure, which later can
350  * be activated or deactivated at will by calls of [dis]connect(intrec*).
351  *
352  * The dev_instance pointer is required for resource management, and will
353  * only be passed through to resource_claim().
354  *
355  * The interrupt handler takes an argument of type (void*), which is not
356  * what is currently used for ISA devices. But since the unit number passed
357  * to an ISA interrupt handler can be stored in a (void*) variable, this
358  * causes no problems. Eventually all the ISA interrupt handlers should be
359  * modified to accept the pointer to their private data, too, instead of
360  * an integer index.
361  *
362  * There will be functions that derive a driver and unit name from a
363  * dev_instance variable, and those functions will be used to maintain the
364  * interrupt counter label array referenced by systat and vmstat to report
365  * device interrupt rates (->update_intrlabels).
366  */
367 
368 intrec *
369 intr_create(void *dev_instance, int irq, inthand2_t handler, void *arg,
370 	     intrmask_t *maskptr, int flags)
371 {
372 	intrec *idesc;
373 
374 	if (ICU_LEN > 8 * sizeof *maskptr) {
375 		printf("create_intr: ICU_LEN of %d too high for %d bit intrmask\n",
376 		       ICU_LEN, 8 * sizeof *maskptr);
377 		return (NULL);
378 	}
379 	if ((unsigned)irq >= ICU_LEN) {
380 		printf("create_intr: requested irq%d too high, limit is %d\n",
381 		       irq, ICU_LEN -1);
382 		return (NULL);
383 	}
384 
385 	idesc = malloc(sizeof *idesc, M_DEVBUF, M_WAITOK);
386 	if (idesc) {
387 		idesc->next     = NULL;
388 		bzero(idesc, sizeof *idesc);
389 
390 		idesc->devdata  = dev_instance;
391 		idesc->handler  = handler;
392 		idesc->argument = arg;
393 		idesc->maskptr  = maskptr;
394 		idesc->intr     = irq;
395 		idesc->flags    = flags;
396 	}
397 	return (idesc);
398 }
399 
400 /*
401  * Return the memory held by the interrupt handler descriptor data structure
402  * to the system. Make sure, the handler is not actively used anymore, before.
403  */
404 
405 int
406 intr_destroy(intrec *rec)
407 {
408 	if (intr_disconnect(rec) != 0)
409 		return (-1);
410 	free(rec, M_DEVBUF);
411 	return (0);
412 }
413 
414 /*
415  * Emulate the register_intr() call previously defined as low level function.
416  * That function (now icu_setup()) may no longer be directly called, since
417  * a conflict between an ISA and PCI interrupt might go by unnocticed, else.
418  */
419 
420 int
421 register_intr(int intr, int device_id, u_int flags,
422 	      inthand2_t handler, u_int *maskptr, int unit)
423 {
424 	/* XXX modify to include isa_device instead of device_id */
425 	intrec *idesc;
426 
427 	flags |= INTR_EXCL;
428 	idesc = intr_create((void *)(intptr_t)device_id, intr, handler,
429 			    (void*)(intptr_t)unit, maskptr, flags);
430 	return (intr_connect(idesc));
431 }
432 
433 /*
434  * Emulate the old unregister_intr() low level function.
435  * Make sure there is just one interrupt, that it was
436  * registered as non-shared, and that the handlers match.
437  */
438 
439 int
440 unregister_intr(int intr, inthand2_t handler)
441 {
442 	intrec *p = intreclist_head[intr];
443 
444 	if (p != NULL && (p->flags & INTR_EXCL) != 0 && p->handler == handler)
445 		return (intr_destroy(p));
446 	return (EINVAL);
447 }
448 
449 #endif /* __i386__ */
450 
451 void
452 register_swi(intr, handler)
453 	int intr;
454 	swihand_t *handler;
455 {
456 	struct swilist *slp, *slq;
457 	int s;
458 
459 	if (intr < NHWI || intr >= NHWI + NSWI)
460 		panic("register_swi: bad intr %d", intr);
461 	if (handler == swi_generic || handler == swi_null)
462 		panic("register_swi: bad handler %p", (void *)handler);
463 	slp = &swilists[intr - NHWI];
464 	s = splhigh();
465 	if (ihandlers[intr] == swi_null)
466 		ihandlers[intr] = handler;
467 	else {
468 		if (slp->sl_next == NULL) {
469 			slp->sl_handler = ihandlers[intr];
470 			ihandlers[intr] = swi_generic;
471 		}
472 		slq = malloc(sizeof(*slq), M_DEVBUF, M_NOWAIT);
473 		if (slq == NULL)
474 			panic("register_swi: malloc failed");
475 		slq->sl_handler = handler;
476 		slq->sl_next = NULL;
477 		while (slp->sl_next != NULL)
478 			slp = slp->sl_next;
479 		slp->sl_next = slq;
480 	}
481 	splx(s);
482 }
483 
484 void
485 swi_dispatcher(intr)
486 	int intr;
487 {
488 	struct swilist *slp;
489 
490 	slp = &swilists[intr - NHWI];
491 	do {
492 		(*slp->sl_handler)();
493 		slp = slp->sl_next;
494 	} while (slp != NULL);
495 }
496 
497 void
498 unregister_swi(intr, handler)
499 	int intr;
500 	swihand_t *handler;
501 {
502 	struct swilist *slfoundpred, *slp, *slq;
503 	int s;
504 
505 	if (intr < NHWI || intr >= NHWI + NSWI)
506 		panic("unregister_swi: bad intr %d", intr);
507 	if (handler == swi_generic || handler == swi_null)
508 		panic("unregister_swi: bad handler %p", (void *)handler);
509 	slp = &swilists[intr - NHWI];
510 	s = splhigh();
511 	if (ihandlers[intr] == handler)
512 		ihandlers[intr] = swi_null;
513 	else if (slp->sl_next != NULL) {
514 		slfoundpred = NULL;
515 		for (slq = slp->sl_next; slq != NULL;
516 		    slp = slq, slq = slp->sl_next)
517 			if (slq->sl_handler == handler)
518 				slfoundpred = slp;
519 		slp = &swilists[intr - NHWI];
520 		if (slfoundpred != NULL) {
521 			slq = slfoundpred->sl_next;
522 			slfoundpred->sl_next = slq->sl_next;
523 			free(slq, M_DEVBUF);
524 		} else if (slp->sl_handler == handler) {
525 			slq = slp->sl_next;
526 			slp->sl_next = slq->sl_next;
527 			slp->sl_handler = slq->sl_handler;
528 			free(slq, M_DEVBUF);
529 		}
530 		if (slp->sl_next == NULL)
531 			ihandlers[intr] = slp->sl_handler;
532 	}
533 	splx(s);
534 }
535 
536