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