xref: /freebsd/sys/x86/isa/atpic.c (revision 17aab35a77a1b1bf02fc85bb8ffadccb0ca5006d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2003 John Baldwin <jhb@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following 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 AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 /*
29  * PIC driver for the 8259A Master and Slave PICs in PC/AT machines.
30  */
31 
32 #include <sys/cdefs.h>
33 #include "opt_auto_eoi.h"
34 #include "opt_isa.h"
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/asan.h>
39 #include <sys/bus.h>
40 #include <sys/interrupt.h>
41 #include <sys/kernel.h>
42 #include <sys/lock.h>
43 #include <sys/module.h>
44 #include <sys/msan.h>
45 
46 #include <machine/cpufunc.h>
47 #include <machine/frame.h>
48 #include <machine/intr_machdep.h>
49 #include <machine/md_var.h>
50 #include <machine/resource.h>
51 #include <machine/segments.h>
52 
53 #include <dev/ic/i8259.h>
54 #include <x86/isa/icu.h>
55 #include <isa/isareg.h>
56 #include <isa/isavar.h>
57 
58 #ifdef __amd64__
59 #define	SDT_ATPIC	SDT_SYSIGT
60 #define	GSEL_ATPIC	0
61 #else
62 #define	SDT_ATPIC	SDT_SYS386IGT
63 #define	GSEL_ATPIC	GSEL(GCODE_SEL, SEL_KPL)
64 #endif
65 
66 #define	MASTER	0
67 #define	SLAVE	1
68 
69 #define	IMEN_MASK(ai)		(IRQ_MASK((ai)->at_irq))
70 
71 #define	NUM_ISA_IRQS		16
72 
73 static void	atpic_init(void *dummy);
74 
75 inthand_t
76 	IDTVEC(atpic_intr0), IDTVEC(atpic_intr1), IDTVEC(atpic_intr2),
77 	IDTVEC(atpic_intr3), IDTVEC(atpic_intr4), IDTVEC(atpic_intr5),
78 	IDTVEC(atpic_intr6), IDTVEC(atpic_intr7), IDTVEC(atpic_intr8),
79 	IDTVEC(atpic_intr9), IDTVEC(atpic_intr10), IDTVEC(atpic_intr11),
80 	IDTVEC(atpic_intr12), IDTVEC(atpic_intr13), IDTVEC(atpic_intr14),
81 	IDTVEC(atpic_intr15);
82 /* XXXKIB i386 uses stubs until pti comes */
83 inthand_t
84 	IDTVEC(atpic_intr0_pti), IDTVEC(atpic_intr1_pti),
85 	IDTVEC(atpic_intr2_pti), IDTVEC(atpic_intr3_pti),
86 	IDTVEC(atpic_intr4_pti), IDTVEC(atpic_intr5_pti),
87 	IDTVEC(atpic_intr6_pti), IDTVEC(atpic_intr7_pti),
88 	IDTVEC(atpic_intr8_pti), IDTVEC(atpic_intr9_pti),
89 	IDTVEC(atpic_intr10_pti), IDTVEC(atpic_intr11_pti),
90 	IDTVEC(atpic_intr12_pti), IDTVEC(atpic_intr13_pti),
91 	IDTVEC(atpic_intr14_pti), IDTVEC(atpic_intr15_pti);
92 
93 #define	IRQ(ap, ai)	((ap)->at_irqbase + (ai)->at_irq)
94 
95 #define	ATPIC(io, base, eoi) {						\
96 		.at_pic = {						\
97 			.pic_register_sources = atpic_register_sources,	\
98 			.pic_enable_source = atpic_enable_source,	\
99 			.pic_disable_source = atpic_disable_source,	\
100 			.pic_eoi_source = (eoi),			\
101 			.pic_enable_intr = atpic_enable_intr,		\
102 			.pic_disable_intr = atpic_disable_intr,		\
103 			.pic_vector = atpic_vector,			\
104 			.pic_source_pending = atpic_source_pending,	\
105 			.pic_resume = atpic_resume,			\
106 			.pic_config_intr = atpic_config_intr,		\
107 			.pic_assign_cpu = atpic_assign_cpu		\
108 		},							\
109 		.at_ioaddr = (io),					\
110 		.at_irqbase = (base),					\
111 		.at_intbase = IDT_IO_INTS + (base),			\
112 		.at_imen = 0xff,					\
113 	}
114 
115 #define	INTSRC(irq)							\
116 	{ { &atpics[(irq) / 8].at_pic }, IDTVEC(atpic_intr ## irq ),	\
117 	    IDTVEC(atpic_intr ## irq ## _pti), (irq) % 8 }
118 
119 struct atpic {
120 	struct pic at_pic;
121 	int	at_ioaddr;
122 	int	at_irqbase;
123 	uint8_t	at_intbase;
124 	uint8_t	at_imen;
125 };
126 
127 struct atpic_intsrc {
128 	struct intsrc at_intsrc;
129 	inthand_t *at_intr, *at_intr_pti;
130 	int	at_irq;			/* Relative to PIC base. */
131 	enum intr_trigger at_trigger;
132 	u_long	at_count;
133 	u_long	at_straycount;
134 };
135 
136 static void atpic_register_sources(struct pic *pic);
137 static void atpic_enable_source(struct intsrc *isrc);
138 static void atpic_disable_source(struct intsrc *isrc, int eoi);
139 static void atpic_eoi_master(struct intsrc *isrc);
140 static void atpic_eoi_slave(struct intsrc *isrc);
141 static void atpic_enable_intr(struct intsrc *isrc);
142 static void atpic_disable_intr(struct intsrc *isrc);
143 static int atpic_vector(struct intsrc *isrc);
144 static void atpic_resume(struct pic *pic, bool suspend_cancelled);
145 static int atpic_source_pending(struct intsrc *isrc);
146 static int atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
147     enum intr_polarity pol);
148 static int atpic_assign_cpu(struct intsrc *isrc, u_int apic_id);
149 static void i8259_init(struct atpic *pic, int slave);
150 
151 static struct atpic atpics[] = {
152 	ATPIC(IO_ICU1, 0, atpic_eoi_master),
153 	ATPIC(IO_ICU2, 8, atpic_eoi_slave)
154 };
155 
156 static struct atpic_intsrc atintrs[] = {
157 	INTSRC(0),
158 	INTSRC(1),
159 	INTSRC(2),
160 	INTSRC(3),
161 	INTSRC(4),
162 	INTSRC(5),
163 	INTSRC(6),
164 	INTSRC(7),
165 	INTSRC(8),
166 	INTSRC(9),
167 	INTSRC(10),
168 	INTSRC(11),
169 	INTSRC(12),
170 	INTSRC(13),
171 	INTSRC(14),
172 	INTSRC(15),
173 };
174 
175 CTASSERT(nitems(atintrs) == NUM_ISA_IRQS);
176 
177 static __inline void
178 _atpic_eoi_master(struct intsrc *isrc)
179 {
180 
181 	KASSERT(isrc->is_pic == &atpics[MASTER].at_pic,
182 	    ("%s: mismatched pic", __func__));
183 #ifndef AUTO_EOI_1
184 	outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
185 #endif
186 }
187 
188 /*
189  * The data sheet says no auto-EOI on slave, but it sometimes works.
190  * So, if AUTO_EOI_2 is enabled, we use it.
191  */
192 static __inline void
193 _atpic_eoi_slave(struct intsrc *isrc)
194 {
195 
196 	KASSERT(isrc->is_pic == &atpics[SLAVE].at_pic,
197 	    ("%s: mismatched pic", __func__));
198 #ifndef AUTO_EOI_2
199 	outb(atpics[SLAVE].at_ioaddr, OCW2_EOI);
200 #ifndef AUTO_EOI_1
201 	outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
202 #endif
203 #endif
204 }
205 
206 static void
207 atpic_register_sources(struct pic *pic)
208 {
209 	struct atpic *ap = (struct atpic *)pic;
210 	struct atpic_intsrc *ai;
211 	int i;
212 
213 	/*
214 	 * If any of the ISA IRQs have an interrupt source already, then
215 	 * assume that the I/O APICs are being used and don't register any
216 	 * of our interrupt sources.  This makes sure we don't accidentally
217 	 * use mixed mode.  The "accidental" use could otherwise occur on
218 	 * machines that route the ACPI SCI interrupt to a different ISA
219 	 * IRQ (at least one machine routes it to IRQ 13) thus disabling
220 	 * that APIC ISA routing and allowing the ATPIC source for that IRQ
221 	 * to leak through.  We used to depend on this feature for routing
222 	 * IRQ0 via mixed mode, but now we don't use mixed mode at all.
223 	 *
224 	 * To avoid the slave not register sources after the master
225 	 * registers its sources, register all IRQs when this function is
226 	 * called on the master.
227 	 */
228 	if (ap != &atpics[MASTER])
229 		return;
230 	for (i = 0; i < NUM_ISA_IRQS; i++)
231 		if (intr_lookup_source(i) != NULL)
232 			return;
233 
234 	/* Loop through all interrupt sources and add them. */
235 	for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
236 		if (i == ICU_SLAVEID)
237 			continue;
238 		intr_register_source(&ai->at_intsrc);
239 	}
240 }
241 
242 static void
243 atpic_enable_source(struct intsrc *isrc)
244 {
245 	struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
246 	struct atpic *ap = (struct atpic *)isrc->is_pic;
247 
248 	spinlock_enter();
249 	if (ap->at_imen & IMEN_MASK(ai)) {
250 		ap->at_imen &= ~IMEN_MASK(ai);
251 		outb(ap->at_ioaddr + ICU_IMR_OFFSET, ap->at_imen);
252 	}
253 	spinlock_exit();
254 }
255 
256 static void
257 atpic_disable_source(struct intsrc *isrc, int eoi)
258 {
259 	struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
260 	struct atpic *ap = (struct atpic *)isrc->is_pic;
261 
262 	spinlock_enter();
263 	if (ai->at_trigger != INTR_TRIGGER_EDGE) {
264 		ap->at_imen |= IMEN_MASK(ai);
265 		outb(ap->at_ioaddr + ICU_IMR_OFFSET, ap->at_imen);
266 	}
267 
268 	/*
269 	 * Take care to call these functions directly instead of through
270 	 * a function pointer.  All of the referenced variables should
271 	 * still be hot in the cache.
272 	 */
273 	if (eoi == PIC_EOI) {
274 		if (isrc->is_pic == &atpics[MASTER].at_pic)
275 			_atpic_eoi_master(isrc);
276 		else
277 			_atpic_eoi_slave(isrc);
278 	}
279 
280 	spinlock_exit();
281 }
282 
283 static void
284 atpic_eoi_master(struct intsrc *isrc)
285 {
286 #ifndef AUTO_EOI_1
287 	spinlock_enter();
288 	_atpic_eoi_master(isrc);
289 	spinlock_exit();
290 #endif
291 }
292 
293 static void
294 atpic_eoi_slave(struct intsrc *isrc)
295 {
296 #ifndef AUTO_EOI_2
297 	spinlock_enter();
298 	_atpic_eoi_slave(isrc);
299 	spinlock_exit();
300 #endif
301 }
302 
303 static void
304 atpic_enable_intr(struct intsrc *isrc)
305 {
306 }
307 
308 static void
309 atpic_disable_intr(struct intsrc *isrc)
310 {
311 }
312 
313 static int
314 atpic_vector(struct intsrc *isrc)
315 {
316 	struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
317 	struct atpic *ap = (struct atpic *)isrc->is_pic;
318 
319 	return (IRQ(ap, ai));
320 }
321 
322 static int
323 atpic_source_pending(struct intsrc *isrc)
324 {
325 	struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
326 	struct atpic *ap = (struct atpic *)isrc->is_pic;
327 
328 	return (inb(ap->at_ioaddr) & IMEN_MASK(ai));
329 }
330 
331 static void
332 atpic_resume(struct pic *pic, bool suspend_cancelled)
333 {
334 	struct atpic *ap = (struct atpic *)pic;
335 
336 	i8259_init(ap, ap == &atpics[SLAVE]);
337 	if (ap == &atpics[SLAVE] && elcr_found)
338 		elcr_resume();
339 }
340 
341 static int
342 atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
343     enum intr_polarity pol)
344 {
345 	struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
346 	u_int vector;
347 
348 	/* Map conforming values to edge/hi and sanity check the values. */
349 	if (trig == INTR_TRIGGER_CONFORM)
350 		trig = INTR_TRIGGER_EDGE;
351 	if (pol == INTR_POLARITY_CONFORM)
352 		pol = INTR_POLARITY_HIGH;
353 	vector = atpic_vector(isrc);
354 	if ((trig == INTR_TRIGGER_EDGE && pol == INTR_POLARITY_LOW) ||
355 	    (trig == INTR_TRIGGER_LEVEL && pol == INTR_POLARITY_HIGH)) {
356 		printf(
357 		"atpic: Mismatched config for IRQ%u: trigger %s, polarity %s\n",
358 		    vector, trig == INTR_TRIGGER_EDGE ? "edge" : "level",
359 		    pol == INTR_POLARITY_HIGH ? "high" : "low");
360 		return (EINVAL);
361 	}
362 
363 	/* If there is no change, just return. */
364 	if (ai->at_trigger == trig)
365 		return (0);
366 
367 	/*
368 	 * Certain IRQs can never be level/lo, so don't try to set them
369 	 * that way if asked.  At least some ELCR registers ignore setting
370 	 * these bits as well.
371 	 */
372 	if ((vector == 0 || vector == 1 || vector == 2 || vector == 13) &&
373 	    trig == INTR_TRIGGER_LEVEL) {
374 		if (bootverbose)
375 			printf(
376 		"atpic: Ignoring invalid level/low configuration for IRQ%u\n",
377 			    vector);
378 		return (EINVAL);
379 	}
380 	if (!elcr_found) {
381 		if (bootverbose)
382 			printf("atpic: No ELCR to configure IRQ%u as %s\n",
383 			    vector, trig == INTR_TRIGGER_EDGE ? "edge/high" :
384 			    "level/low");
385 		return (ENXIO);
386 	}
387 	if (bootverbose)
388 		printf("atpic: Programming IRQ%u as %s\n", vector,
389 		    trig == INTR_TRIGGER_EDGE ? "edge/high" : "level/low");
390 	spinlock_enter();
391 	elcr_write_trigger(atpic_vector(isrc), trig);
392 	ai->at_trigger = trig;
393 	spinlock_exit();
394 	return (0);
395 }
396 
397 static int
398 atpic_assign_cpu(struct intsrc *isrc, u_int apic_id)
399 {
400 
401 	/*
402 	 * 8259A's are only used in UP in which case all interrupts always
403 	 * go to the sole CPU and this function shouldn't even be called.
404 	 */
405 	panic("%s: bad cookie", __func__);
406 }
407 
408 static void
409 i8259_init(struct atpic *pic, int slave)
410 {
411 	int imr_addr;
412 
413 	/* Reset the PIC and program with next four bytes. */
414 	spinlock_enter();
415 	outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4);
416 	imr_addr = pic->at_ioaddr + ICU_IMR_OFFSET;
417 
418 	/* Start vector. */
419 	outb(imr_addr, pic->at_intbase);
420 
421 	/*
422 	 * Setup slave links.  For the master pic, indicate what line
423 	 * the slave is configured on.  For the slave indicate
424 	 * which line on the master we are connected to.
425 	 */
426 	if (slave)
427 		outb(imr_addr, ICU_SLAVEID);
428 	else
429 		outb(imr_addr, IRQ_MASK(ICU_SLAVEID));
430 
431 	/* Set mode. */
432 	if (slave)
433 		outb(imr_addr, SLAVE_MODE);
434 	else
435 		outb(imr_addr, MASTER_MODE);
436 
437 	/* Set interrupt enable mask. */
438 	outb(imr_addr, pic->at_imen);
439 
440 	/* Reset is finished, default to IRR on read. */
441 	outb(pic->at_ioaddr, OCW3_SEL | OCW3_RR);
442 
443 	/* OCW2_L1 sets priority order to 3-7, 0-2 (com2 first). */
444 	if (!slave)
445 		outb(pic->at_ioaddr, OCW2_R | OCW2_SL | OCW2_L1);
446 
447 	spinlock_exit();
448 }
449 
450 void
451 atpic_startup(void)
452 {
453 	struct atpic_intsrc *ai;
454 	int i;
455 
456 	/* Start off with all interrupts disabled. */
457 	i8259_init(&atpics[MASTER], 0);
458 	i8259_init(&atpics[SLAVE], 1);
459 	atpic_enable_source((struct intsrc *)&atintrs[ICU_SLAVEID]);
460 
461 	/* Install low-level interrupt handlers for all of our IRQs. */
462 	for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
463 		if (i == ICU_SLAVEID)
464 			continue;
465 		ai->at_intsrc.is_count = &ai->at_count;
466 		ai->at_intsrc.is_straycount = &ai->at_straycount;
467 		setidt(((struct atpic *)ai->at_intsrc.is_pic)->at_intbase +
468 		    ai->at_irq, pti ? ai->at_intr_pti : ai->at_intr, SDT_ATPIC,
469 		    SEL_KPL, GSEL_ATPIC);
470 	}
471 
472 	/*
473 	 * Look for an ELCR.  If we find one, update the trigger modes.
474 	 * If we don't find one, assume that IRQs 0, 1, 2, and 13 are
475 	 * edge triggered and that everything else is level triggered.
476 	 * We only use the trigger information to reprogram the ELCR if
477 	 * we have one and as an optimization to avoid masking edge
478 	 * triggered interrupts.  For the case that we don't have an ELCR,
479 	 * it doesn't hurt to mask an edge triggered interrupt, so we
480 	 * assume level trigger for any interrupt that we aren't sure is
481 	 * edge triggered.
482 	 */
483 	if (elcr_found) {
484 		for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
485 			ai->at_trigger = elcr_read_trigger(i);
486 	} else {
487 		for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
488 			switch (i) {
489 			case 0:
490 			case 1:
491 			case 2:
492 			case 8:
493 			case 13:
494 				ai->at_trigger = INTR_TRIGGER_EDGE;
495 				break;
496 			default:
497 				ai->at_trigger = INTR_TRIGGER_LEVEL;
498 				break;
499 			}
500 	}
501 }
502 
503 static void
504 atpic_init(void *dummy __unused)
505 {
506 
507 	/*
508 	 * Register our PICs, even if we aren't going to use any of their
509 	 * pins so that they are suspended and resumed.
510 	 */
511 	if (intr_register_pic(&atpics[0].at_pic) != 0 ||
512 	    intr_register_pic(&atpics[1].at_pic) != 0)
513 		panic("Unable to register ATPICs");
514 
515 	if (num_io_irqs == 0)
516 		num_io_irqs = NUM_ISA_IRQS;
517 }
518 SYSINIT(atpic_init, SI_SUB_INTR, SI_ORDER_FOURTH, atpic_init, NULL);
519 
520 void
521 atpic_handle_intr(u_int vector, struct trapframe *frame)
522 {
523 	struct intsrc *isrc;
524 
525 	kasan_mark(frame, sizeof(*frame), sizeof(*frame), 0);
526 	kmsan_mark(frame, sizeof(*frame), KMSAN_STATE_INITED);
527 	trap_check_kstack();
528 
529 	KASSERT(vector < NUM_ISA_IRQS, ("unknown int %u\n", vector));
530 	isrc = &atintrs[vector].at_intsrc;
531 
532 	/*
533 	 * If we don't have an event, see if this is a spurious
534 	 * interrupt.
535 	 */
536 	if (isrc->is_event == NULL && (vector == 7 || vector == 15)) {
537 		int port, isr;
538 
539 		/*
540 		 * Read the ISR register to see if IRQ 7/15 is really
541 		 * pending.  Reset read register back to IRR when done.
542 		 */
543 		port = ((struct atpic *)isrc->is_pic)->at_ioaddr;
544 		spinlock_enter();
545 		outb(port, OCW3_SEL | OCW3_RR | OCW3_RIS);
546 		isr = inb(port);
547 		outb(port, OCW3_SEL | OCW3_RR);
548 		spinlock_exit();
549 		if ((isr & IRQ_MASK(7)) == 0)
550 			return;
551 	}
552 	intr_execute_handlers(isrc, frame);
553 }
554 
555 #ifdef DEV_ISA
556 /*
557  * Bus attachment for the ISA PIC.
558  */
559 static struct isa_pnp_id atpic_ids[] = {
560 	{ 0x0000d041 /* PNP0000 */, "AT interrupt controller" },
561 	{ 0 }
562 };
563 
564 static int
565 atpic_probe(device_t dev)
566 {
567 	int result;
568 
569 	result = ISA_PNP_PROBE(device_get_parent(dev), dev, atpic_ids);
570 	if (result <= 0)
571 		device_quiet(dev);
572 	return (result);
573 }
574 
575 /*
576  * We might be granted IRQ 2, as this is typically consumed by chaining
577  * between the two PIC components.  If we're using the APIC, however,
578  * this may not be the case, and as such we should free the resource.
579  * (XXX untested)
580  *
581  * The generic ISA attachment code will handle allocating any other resources
582  * that we don't explicitly claim here.
583  */
584 static int
585 atpic_attach(device_t dev)
586 {
587 	struct resource *res;
588 	int rid;
589 
590 	/* Try to allocate our IRQ and then free it. */
591 	rid = 0;
592 	res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 0);
593 	if (res != NULL)
594 		bus_release_resource(dev, SYS_RES_IRQ, rid, res);
595 	return (0);
596 }
597 
598 static device_method_t atpic_methods[] = {
599 	/* Device interface */
600 	DEVMETHOD(device_probe,		atpic_probe),
601 	DEVMETHOD(device_attach,	atpic_attach),
602 	{ 0, 0 }
603 };
604 
605 static driver_t atpic_driver = {
606 	"atpic",
607 	atpic_methods,
608 	1,		/* no softc */
609 };
610 
611 DRIVER_MODULE(atpic, isa, atpic_driver, 0, 0);
612 DRIVER_MODULE(atpic, acpi, atpic_driver, 0, 0);
613 ISA_PNP_INFO(atpic_ids);
614 #endif /* DEV_ISA */
615