kern_intr.c revision 27297
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
49typedef 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
66static inline intrmask_t
67splq(intrmask_t mask)
68{
69	intrmask_t tmp = cpl;
70	cpl |= mask;
71	return (tmp);
72}
73
74static void
75intr_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 ??? */
89static intrec *intreclist_head[ICU_LEN];
90
91static intrec*
92find_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
102static intrec**
103find_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
132static void
133update_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
148static void
149update_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
180static int
181add_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
236int
237intr_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
277int
278intr_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
353intrec *
354intr_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
390int
391intr_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
405int
406register_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
424int
425unregister_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