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