kern_intr.c revision 41059
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.20 1998/09/26 14:25:31 dfr Exp $
27 *
28 */
29
30
31#include <sys/param.h>
32#include <sys/systm.h>
33#include <sys/malloc.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
52typedef 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
63static intrec *intreclist_head[NHWI];
64
65#endif
66
67struct swilist {
68	swihand_t	*sl_handler;
69	struct swilist	*sl_next;
70};
71
72static 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
83static __inline intrmask_t
84splq(intrmask_t mask)
85{
86	intrmask_t tmp = cpl;
87	cpl |= mask;
88	return (tmp);
89}
90#endif /* SMP */
91
92static void
93intr_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
105static intrec*
106find_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
116static intrec**
117find_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
146static void
147update_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
162static void
163update_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
194static int
195add_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
251int
252intr_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
292int
293intr_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
368intrec *
369intr_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
405int
406intr_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
420int
421register_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
439int
440unregister_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
451void
452register_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
484void
485swi_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
497void
498unregister_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