23cec608a6
- Split the intr_table_lock into an sx lock used for most things, and a spin lock to protect intrcnt_index. Originally I had this as a spin lock so interrupt code could use it to lookup sources. However, we don't actually do that because it would add a lot of overhead to interrupts, and if we ever do support removing interrupt sources, we can use other means to safely do so w/o locking in the interrupt handling code. - Replace is_enabled (boolean) with is_handlers (a count of handlers) to determine if a source is enabled or not. This allows us to notice when a source is no longer in use. When that happens, we now invoke a new PIC method (pic_disable_intr()) to inform the PIC driver that the source is no longer in use. The I/O APIC driver frees the APIC IDT vector when this happens. The MSI driver no longer needs to have a hack to clear is_enabled during msi_alloc() and msix_alloc() as a result of this change as well. - Add an apic_disable_vector() to reset an IDT vector back to Xrsvd to complement apic_enable_vector() and use it in the I/O APIC and MSI code when freeing an IDT vector. - Add a new nexus hook: nexus_add_irq() to ask the nexus driver to add an IRQ to its irq_rman. The MSI code uses this when it creates new interrupt sources to let the nexus know about newly valid IRQs. Previously the msi_alloc() and msix_alloc() passed some extra stuff back to the nexus methods which then added the IRQs. This approach is a bit cleaner. - Change the MSI sx lock to a mutex. If we need to create new sources, drop the lock, create the required number of sources, then get the lock and try the allocation again.
681 lines
18 KiB
C
681 lines
18 KiB
C
/*-
|
|
* Copyright (c) 2003 John Baldwin <jhb@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the author nor the names of any co-contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* PIC driver for the 8259A Master and Slave PICs in PC/AT machines.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_auto_eoi.h"
|
|
#include "opt_isa.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/interrupt.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/module.h>
|
|
#include <sys/mutex.h>
|
|
|
|
#include <machine/cpufunc.h>
|
|
#include <machine/frame.h>
|
|
#include <machine/intr_machdep.h>
|
|
#include <machine/md_var.h>
|
|
#include <machine/resource.h>
|
|
#include <machine/segments.h>
|
|
|
|
#include <dev/ic/i8259.h>
|
|
#include <i386/isa/icu.h>
|
|
#ifdef PC98
|
|
#include <pc98/cbus/cbus.h>
|
|
#else
|
|
#include <i386/isa/isa.h>
|
|
#endif
|
|
#include <isa/isavar.h>
|
|
|
|
#define MASTER 0
|
|
#define SLAVE 1
|
|
|
|
/*
|
|
* PC-98 machines wire the slave 8259A to pin 7 on the master PIC, and
|
|
* PC-AT machines wire the slave PIC to pin 2 on the master PIC.
|
|
*/
|
|
#ifdef PC98
|
|
#define ICU_SLAVEID 7
|
|
#else
|
|
#define ICU_SLAVEID 2
|
|
#endif
|
|
|
|
/*
|
|
* Determine the base master and slave modes not including auto EOI support.
|
|
* All machines that FreeBSD supports use 8086 mode.
|
|
*/
|
|
#ifdef PC98
|
|
/*
|
|
* PC-98 machines do not support auto EOI on the second PIC. Also, it
|
|
* seems that PC-98 machine PICs use buffered mode, and the master PIC
|
|
* uses special fully nested mode.
|
|
*/
|
|
#define BASE_MASTER_MODE (ICW4_SFNM | ICW4_BUF | ICW4_MS | ICW4_8086)
|
|
#define BASE_SLAVE_MODE (ICW4_BUF | ICW4_8086)
|
|
#else
|
|
#define BASE_MASTER_MODE ICW4_8086
|
|
#define BASE_SLAVE_MODE ICW4_8086
|
|
#endif
|
|
|
|
/* Enable automatic EOI if requested. */
|
|
#ifdef AUTO_EOI_1
|
|
#define MASTER_MODE (BASE_MASTER_MODE | ICW4_AEOI)
|
|
#else
|
|
#define MASTER_MODE BASE_MASTER_MODE
|
|
#endif
|
|
#ifdef AUTO_EOI_2
|
|
#define SLAVE_MODE (BASE_SLAVE_MODE | ICW4_AEOI)
|
|
#else
|
|
#define SLAVE_MODE BASE_SLAVE_MODE
|
|
#endif
|
|
|
|
#define IRQ_MASK(irq) (1 << (irq))
|
|
#define IMEN_MASK(ai) (IRQ_MASK((ai)->at_irq))
|
|
|
|
#define NUM_ISA_IRQS 16
|
|
|
|
static void atpic_init(void *dummy);
|
|
|
|
unsigned int imen; /* XXX */
|
|
|
|
inthand_t
|
|
IDTVEC(atpic_intr0), IDTVEC(atpic_intr1), IDTVEC(atpic_intr2),
|
|
IDTVEC(atpic_intr3), IDTVEC(atpic_intr4), IDTVEC(atpic_intr5),
|
|
IDTVEC(atpic_intr6), IDTVEC(atpic_intr7), IDTVEC(atpic_intr8),
|
|
IDTVEC(atpic_intr9), IDTVEC(atpic_intr10), IDTVEC(atpic_intr11),
|
|
IDTVEC(atpic_intr12), IDTVEC(atpic_intr13), IDTVEC(atpic_intr14),
|
|
IDTVEC(atpic_intr15);
|
|
|
|
#define IRQ(ap, ai) ((ap)->at_irqbase + (ai)->at_irq)
|
|
|
|
#define ATPIC(io, base, eoi, imenptr) \
|
|
{ { atpic_enable_source, atpic_disable_source, (eoi), \
|
|
atpic_enable_intr, atpic_disable_intr, atpic_vector, \
|
|
atpic_source_pending, NULL, atpic_resume, atpic_config_intr,\
|
|
atpic_assign_cpu }, (io), (base), IDT_IO_INTS + (base), \
|
|
(imenptr) }
|
|
|
|
#define INTSRC(irq) \
|
|
{ { &atpics[(irq) / 8].at_pic }, IDTVEC(atpic_intr ## irq ), \
|
|
(irq) % 8 }
|
|
|
|
struct atpic {
|
|
struct pic at_pic;
|
|
int at_ioaddr;
|
|
int at_irqbase;
|
|
uint8_t at_intbase;
|
|
uint8_t *at_imen;
|
|
};
|
|
|
|
struct atpic_intsrc {
|
|
struct intsrc at_intsrc;
|
|
inthand_t *at_intr;
|
|
int at_irq; /* Relative to PIC base. */
|
|
enum intr_trigger at_trigger;
|
|
u_long at_count;
|
|
u_long at_straycount;
|
|
};
|
|
|
|
static void atpic_enable_source(struct intsrc *isrc);
|
|
static void atpic_disable_source(struct intsrc *isrc, int eoi);
|
|
static void atpic_eoi_master(struct intsrc *isrc);
|
|
static void atpic_eoi_slave(struct intsrc *isrc);
|
|
static void atpic_enable_intr(struct intsrc *isrc);
|
|
static void atpic_disable_intr(struct intsrc *isrc);
|
|
static int atpic_vector(struct intsrc *isrc);
|
|
static void atpic_resume(struct pic *pic);
|
|
static int atpic_source_pending(struct intsrc *isrc);
|
|
static int atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
|
|
enum intr_polarity pol);
|
|
static void atpic_assign_cpu(struct intsrc *isrc, u_int apic_id);
|
|
static void i8259_init(struct atpic *pic, int slave);
|
|
|
|
static struct atpic atpics[] = {
|
|
ATPIC(IO_ICU1, 0, atpic_eoi_master, (uint8_t *)&imen),
|
|
ATPIC(IO_ICU2, 8, atpic_eoi_slave, ((uint8_t *)&imen) + 1)
|
|
};
|
|
|
|
static struct atpic_intsrc atintrs[] = {
|
|
INTSRC(0),
|
|
INTSRC(1),
|
|
INTSRC(2),
|
|
INTSRC(3),
|
|
INTSRC(4),
|
|
INTSRC(5),
|
|
INTSRC(6),
|
|
INTSRC(7),
|
|
INTSRC(8),
|
|
INTSRC(9),
|
|
INTSRC(10),
|
|
INTSRC(11),
|
|
INTSRC(12),
|
|
INTSRC(13),
|
|
INTSRC(14),
|
|
INTSRC(15),
|
|
};
|
|
|
|
CTASSERT(sizeof(atintrs) / sizeof(atintrs[0]) == NUM_ISA_IRQS);
|
|
|
|
static __inline void
|
|
_atpic_eoi_master(struct intsrc *isrc)
|
|
{
|
|
|
|
KASSERT(isrc->is_pic == &atpics[MASTER].at_pic,
|
|
("%s: mismatched pic", __func__));
|
|
#ifndef AUTO_EOI_1
|
|
outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* The data sheet says no auto-EOI on slave, but it sometimes works.
|
|
* So, if AUTO_EOI_2 is enabled, we use it.
|
|
*/
|
|
static __inline void
|
|
_atpic_eoi_slave(struct intsrc *isrc)
|
|
{
|
|
|
|
KASSERT(isrc->is_pic == &atpics[SLAVE].at_pic,
|
|
("%s: mismatched pic", __func__));
|
|
#ifndef AUTO_EOI_2
|
|
outb(atpics[SLAVE].at_ioaddr, OCW2_EOI);
|
|
#ifndef AUTO_EOI_1
|
|
outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
atpic_enable_source(struct intsrc *isrc)
|
|
{
|
|
struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
|
|
struct atpic *ap = (struct atpic *)isrc->is_pic;
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
if (*ap->at_imen & IMEN_MASK(ai)) {
|
|
*ap->at_imen &= ~IMEN_MASK(ai);
|
|
outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen);
|
|
}
|
|
mtx_unlock_spin(&icu_lock);
|
|
}
|
|
|
|
static void
|
|
atpic_disable_source(struct intsrc *isrc, int eoi)
|
|
{
|
|
struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
|
|
struct atpic *ap = (struct atpic *)isrc->is_pic;
|
|
|
|
mtx_lock_spin(&icu_lock);
|
|
if (ai->at_trigger != INTR_TRIGGER_EDGE) {
|
|
*ap->at_imen |= IMEN_MASK(ai);
|
|
outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen);
|
|
}
|
|
|
|
/*
|
|
* Take care to call these functions directly instead of through
|
|
* a function pointer. All of the referenced variables should
|
|
* still be hot in the cache.
|
|
*/
|
|
if (eoi == PIC_EOI) {
|
|
if (isrc->is_pic == &atpics[MASTER].at_pic)
|
|
_atpic_eoi_master(isrc);
|
|
else
|
|
_atpic_eoi_slave(isrc);
|
|
}
|
|
|
|
mtx_unlock_spin(&icu_lock);
|
|
}
|
|
|
|
static void
|
|
atpic_eoi_master(struct intsrc *isrc)
|
|
{
|
|
#ifndef AUTO_EOI_1
|
|
mtx_lock_spin(&icu_lock);
|
|
_atpic_eoi_master(isrc);
|
|
mtx_unlock_spin(&icu_lock);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
atpic_eoi_slave(struct intsrc *isrc)
|
|
{
|
|
#ifndef AUTO_EOI_2
|
|
mtx_lock_spin(&icu_lock);
|
|
_atpic_eoi_slave(isrc);
|
|
mtx_unlock_spin(&icu_lock);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
atpic_enable_intr(struct intsrc *isrc)
|
|
{
|
|
}
|
|
|
|
static void
|
|
atpic_disable_intr(struct intsrc *isrc)
|
|
{
|
|
}
|
|
|
|
|
|
static int
|
|
atpic_vector(struct intsrc *isrc)
|
|
{
|
|
struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
|
|
struct atpic *ap = (struct atpic *)isrc->is_pic;
|
|
|
|
return (IRQ(ap, ai));
|
|
}
|
|
|
|
static int
|
|
atpic_source_pending(struct intsrc *isrc)
|
|
{
|
|
struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
|
|
struct atpic *ap = (struct atpic *)isrc->is_pic;
|
|
|
|
return (inb(ap->at_ioaddr) & IMEN_MASK(ai));
|
|
}
|
|
|
|
static void
|
|
atpic_resume(struct pic *pic)
|
|
{
|
|
struct atpic *ap = (struct atpic *)pic;
|
|
|
|
i8259_init(ap, ap == &atpics[SLAVE]);
|
|
#ifndef PC98
|
|
if (ap == &atpics[SLAVE] && elcr_found)
|
|
elcr_resume();
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
|
|
enum intr_polarity pol)
|
|
{
|
|
struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
|
|
u_int vector;
|
|
|
|
/* Map conforming values to edge/hi and sanity check the values. */
|
|
if (trig == INTR_TRIGGER_CONFORM)
|
|
trig = INTR_TRIGGER_EDGE;
|
|
if (pol == INTR_POLARITY_CONFORM)
|
|
pol = INTR_POLARITY_HIGH;
|
|
vector = atpic_vector(isrc);
|
|
if ((trig == INTR_TRIGGER_EDGE && pol == INTR_POLARITY_LOW) ||
|
|
(trig == INTR_TRIGGER_LEVEL && pol == INTR_POLARITY_HIGH)) {
|
|
printf(
|
|
"atpic: Mismatched config for IRQ%u: trigger %s, polarity %s\n",
|
|
vector, trig == INTR_TRIGGER_EDGE ? "edge" : "level",
|
|
pol == INTR_POLARITY_HIGH ? "high" : "low");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* If there is no change, just return. */
|
|
if (ai->at_trigger == trig)
|
|
return (0);
|
|
|
|
#ifdef PC98
|
|
if ((vector == 0 || vector == 1 || vector == 7 || vector == 8) &&
|
|
trig == INTR_TRIGGER_LEVEL) {
|
|
if (bootverbose)
|
|
printf(
|
|
"atpic: Ignoring invalid level/low configuration for IRQ%u\n",
|
|
vector);
|
|
return (EINVAL);
|
|
}
|
|
return (ENXIO);
|
|
#else
|
|
/*
|
|
* Certain IRQs can never be level/lo, so don't try to set them
|
|
* that way if asked. At least some ELCR registers ignore setting
|
|
* these bits as well.
|
|
*/
|
|
if ((vector == 0 || vector == 1 || vector == 2 || vector == 13) &&
|
|
trig == INTR_TRIGGER_LEVEL) {
|
|
if (bootverbose)
|
|
printf(
|
|
"atpic: Ignoring invalid level/low configuration for IRQ%u\n",
|
|
vector);
|
|
return (EINVAL);
|
|
}
|
|
if (!elcr_found) {
|
|
if (bootverbose)
|
|
printf("atpic: No ELCR to configure IRQ%u as %s\n",
|
|
vector, trig == INTR_TRIGGER_EDGE ? "edge/high" :
|
|
"level/low");
|
|
return (ENXIO);
|
|
}
|
|
if (bootverbose)
|
|
printf("atpic: Programming IRQ%u as %s\n", vector,
|
|
trig == INTR_TRIGGER_EDGE ? "edge/high" : "level/low");
|
|
mtx_lock_spin(&icu_lock);
|
|
elcr_write_trigger(atpic_vector(isrc), trig);
|
|
ai->at_trigger = trig;
|
|
mtx_unlock_spin(&icu_lock);
|
|
return (0);
|
|
#endif /* PC98 */
|
|
}
|
|
|
|
static void
|
|
atpic_assign_cpu(struct intsrc *isrc, u_int apic_id)
|
|
{
|
|
|
|
/*
|
|
* 8259A's are only used in UP in which case all interrupts always
|
|
* go to the sole CPU and this function shouldn't even be called.
|
|
*/
|
|
panic("%s: bad cookie", __func__);
|
|
}
|
|
|
|
static void
|
|
i8259_init(struct atpic *pic, int slave)
|
|
{
|
|
int imr_addr;
|
|
|
|
/* Reset the PIC and program with next four bytes. */
|
|
mtx_lock_spin(&icu_lock);
|
|
#ifdef DEV_MCA
|
|
/* MCA uses level triggered interrupts. */
|
|
if (MCA_system)
|
|
outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4 | ICW1_LTIM);
|
|
else
|
|
#endif
|
|
outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4);
|
|
imr_addr = pic->at_ioaddr + ICU_IMR_OFFSET;
|
|
|
|
/* Start vector. */
|
|
outb(imr_addr, pic->at_intbase);
|
|
|
|
/*
|
|
* Setup slave links. For the master pic, indicate what line
|
|
* the slave is configured on. For the slave indicate
|
|
* which line on the master we are connected to.
|
|
*/
|
|
if (slave)
|
|
outb(imr_addr, ICU_SLAVEID);
|
|
else
|
|
outb(imr_addr, IRQ_MASK(ICU_SLAVEID));
|
|
|
|
/* Set mode. */
|
|
if (slave)
|
|
outb(imr_addr, SLAVE_MODE);
|
|
else
|
|
outb(imr_addr, MASTER_MODE);
|
|
|
|
/* Set interrupt enable mask. */
|
|
outb(imr_addr, *pic->at_imen);
|
|
|
|
/* Reset is finished, default to IRR on read. */
|
|
outb(pic->at_ioaddr, OCW3_SEL | OCW3_RR);
|
|
|
|
#ifndef PC98
|
|
/* OCW2_L1 sets priority order to 3-7, 0-2 (com2 first). */
|
|
if (!slave)
|
|
outb(pic->at_ioaddr, OCW2_R | OCW2_SL | OCW2_L1);
|
|
#endif
|
|
mtx_unlock_spin(&icu_lock);
|
|
}
|
|
|
|
void
|
|
atpic_startup(void)
|
|
{
|
|
struct atpic_intsrc *ai;
|
|
int i;
|
|
|
|
/* Start off with all interrupts disabled. */
|
|
imen = 0xffff;
|
|
i8259_init(&atpics[MASTER], 0);
|
|
i8259_init(&atpics[SLAVE], 1);
|
|
atpic_enable_source((struct intsrc *)&atintrs[ICU_SLAVEID]);
|
|
|
|
/* Install low-level interrupt handlers for all of our IRQs. */
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
|
|
if (i == ICU_SLAVEID)
|
|
continue;
|
|
ai->at_intsrc.is_count = &ai->at_count;
|
|
ai->at_intsrc.is_straycount = &ai->at_straycount;
|
|
setidt(((struct atpic *)ai->at_intsrc.is_pic)->at_intbase +
|
|
ai->at_irq, ai->at_intr, SDT_SYS386IGT, SEL_KPL,
|
|
GSEL(GCODE_SEL, SEL_KPL));
|
|
}
|
|
|
|
#ifdef DEV_MCA
|
|
/* For MCA systems, all interrupts are level triggered. */
|
|
if (MCA_system)
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
|
|
ai->at_trigger = INTR_TRIGGER_LEVEL;
|
|
else
|
|
#endif
|
|
|
|
#ifdef PC98
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
|
|
switch (i) {
|
|
case 0:
|
|
case 1:
|
|
case 7:
|
|
case 8:
|
|
ai->at_trigger = INTR_TRIGGER_EDGE;
|
|
break;
|
|
default:
|
|
ai->at_trigger = INTR_TRIGGER_LEVEL;
|
|
break;
|
|
}
|
|
#else
|
|
/*
|
|
* Look for an ELCR. If we find one, update the trigger modes.
|
|
* If we don't find one, assume that IRQs 0, 1, 2, and 13 are
|
|
* edge triggered and that everything else is level triggered.
|
|
* We only use the trigger information to reprogram the ELCR if
|
|
* we have one and as an optimization to avoid masking edge
|
|
* triggered interrupts. For the case that we don't have an ELCR,
|
|
* it doesn't hurt to mask an edge triggered interrupt, so we
|
|
* assume level trigger for any interrupt that we aren't sure is
|
|
* edge triggered.
|
|
*/
|
|
if (elcr_found) {
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
|
|
ai->at_trigger = elcr_read_trigger(i);
|
|
} else {
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
|
|
switch (i) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 8:
|
|
case 13:
|
|
ai->at_trigger = INTR_TRIGGER_EDGE;
|
|
break;
|
|
default:
|
|
ai->at_trigger = INTR_TRIGGER_LEVEL;
|
|
break;
|
|
}
|
|
}
|
|
#endif /* PC98 */
|
|
}
|
|
|
|
static void
|
|
atpic_init(void *dummy __unused)
|
|
{
|
|
struct atpic_intsrc *ai;
|
|
int i;
|
|
|
|
/*
|
|
* Register our PICs, even if we aren't going to use any of their
|
|
* pins so that they are suspended and resumed.
|
|
*/
|
|
if (intr_register_pic(&atpics[0].at_pic) != 0 ||
|
|
intr_register_pic(&atpics[1].at_pic) != 0)
|
|
panic("Unable to register ATPICs");
|
|
|
|
/*
|
|
* If any of the ISA IRQs have an interrupt source already, then
|
|
* assume that the APICs are being used and don't register any
|
|
* of our interrupt sources. This makes sure we don't accidentally
|
|
* use mixed mode. The "accidental" use could otherwise occur on
|
|
* machines that route the ACPI SCI interrupt to a different ISA
|
|
* IRQ (at least one machines routes it to IRQ 13) thus disabling
|
|
* that APIC ISA routing and allowing the ATPIC source for that IRQ
|
|
* to leak through. We used to depend on this feature for routing
|
|
* IRQ0 via mixed mode, but now we don't use mixed mode at all.
|
|
*/
|
|
for (i = 0; i < NUM_ISA_IRQS; i++)
|
|
if (intr_lookup_source(i) != NULL)
|
|
return;
|
|
|
|
/* Loop through all interrupt sources and add them. */
|
|
for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
|
|
if (i == ICU_SLAVEID)
|
|
continue;
|
|
intr_register_source(&ai->at_intsrc);
|
|
}
|
|
}
|
|
SYSINIT(atpic_init, SI_SUB_INTR, SI_ORDER_SECOND + 1, atpic_init, NULL)
|
|
|
|
void
|
|
atpic_handle_intr(u_int vector, struct trapframe *frame)
|
|
{
|
|
struct intsrc *isrc;
|
|
|
|
KASSERT(vector < NUM_ISA_IRQS, ("unknown int %u\n", vector));
|
|
isrc = &atintrs[vector].at_intsrc;
|
|
|
|
/*
|
|
* If we don't have an event, see if this is a spurious
|
|
* interrupt.
|
|
*/
|
|
if (isrc->is_event == NULL && (vector == 7 || vector == 15)) {
|
|
int port, isr;
|
|
|
|
/*
|
|
* Read the ISR register to see if IRQ 7/15 is really
|
|
* pending. Reset read register back to IRR when done.
|
|
*/
|
|
port = ((struct atpic *)isrc->is_pic)->at_ioaddr;
|
|
mtx_lock_spin(&icu_lock);
|
|
outb(port, OCW3_SEL | OCW3_RR | OCW3_RIS);
|
|
isr = inb(port);
|
|
outb(port, OCW3_SEL | OCW3_RR);
|
|
mtx_unlock_spin(&icu_lock);
|
|
if ((isr & IRQ_MASK(7)) == 0)
|
|
return;
|
|
}
|
|
intr_execute_handlers(isrc, frame);
|
|
}
|
|
|
|
#ifdef DEV_ISA
|
|
/*
|
|
* Bus attachment for the ISA PIC.
|
|
*/
|
|
static struct isa_pnp_id atpic_ids[] = {
|
|
{ 0x0000d041 /* PNP0000 */, "AT interrupt controller" },
|
|
{ 0 }
|
|
};
|
|
|
|
static int
|
|
atpic_probe(device_t dev)
|
|
{
|
|
int result;
|
|
|
|
result = ISA_PNP_PROBE(device_get_parent(dev), dev, atpic_ids);
|
|
if (result <= 0)
|
|
device_quiet(dev);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* We might be granted IRQ 2, as this is typically consumed by chaining
|
|
* between the two PIC components. If we're using the APIC, however,
|
|
* this may not be the case, and as such we should free the resource.
|
|
* (XXX untested)
|
|
*
|
|
* The generic ISA attachment code will handle allocating any other resources
|
|
* that we don't explicitly claim here.
|
|
*/
|
|
static int
|
|
atpic_attach(device_t dev)
|
|
{
|
|
struct resource *res;
|
|
int rid;
|
|
|
|
/* Try to allocate our IRQ and then free it. */
|
|
rid = 0;
|
|
res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 0);
|
|
if (res != NULL)
|
|
bus_release_resource(dev, SYS_RES_IRQ, rid, res);
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t atpic_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, atpic_probe),
|
|
DEVMETHOD(device_attach, atpic_attach),
|
|
DEVMETHOD(device_detach, bus_generic_detach),
|
|
DEVMETHOD(device_shutdown, bus_generic_shutdown),
|
|
DEVMETHOD(device_suspend, bus_generic_suspend),
|
|
DEVMETHOD(device_resume, bus_generic_resume),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t atpic_driver = {
|
|
"atpic",
|
|
atpic_methods,
|
|
1, /* no softc */
|
|
};
|
|
|
|
static devclass_t atpic_devclass;
|
|
|
|
DRIVER_MODULE(atpic, isa, atpic_driver, atpic_devclass, 0, 0);
|
|
#ifndef PC98
|
|
DRIVER_MODULE(atpic, acpi, atpic_driver, atpic_devclass, 0, 0);
|
|
#endif
|
|
|
|
/*
|
|
* Return a bitmap of the current interrupt requests. This is 8259-specific
|
|
* and is only suitable for use at probe time.
|
|
*/
|
|
intrmask_t
|
|
isa_irq_pending(void)
|
|
{
|
|
u_char irr1;
|
|
u_char irr2;
|
|
|
|
irr1 = inb(IO_ICU1);
|
|
irr2 = inb(IO_ICU2);
|
|
return ((irr2 << 8) | irr1);
|
|
}
|
|
#endif /* DEV_ISA */
|