Fix interrupt handling. It started off broken and grew worse over time.

The rewrite of the interrupt handler includes:
o   loop until all pending interrupts are handled. This closes a
    race condition.
o   count the number of interrupt sources we handled so that we can
    properly return FILTER_HANDLED or FILTER_STRAY when we break out
    of the loop.
o   When matching the interrupt source to the devices that have that
    source pending, check only from the set of devices we found to
    have a pending interrupt.

PR:		kern/140947
MFC after:	3 days
This commit is contained in:
Marcel Moolenaar 2009-12-11 03:08:07 +00:00
parent ae6de8d67a
commit 57467e5933
1 changed files with 65 additions and 49 deletions

View File

@ -129,62 +129,78 @@ puc_intr(void *arg)
{
struct puc_port *port;
struct puc_softc *sc = arg;
u_long dev, devs;
int i, idx, ipend, isrc;
u_long ds, dev, devs;
int i, idx, ipend, isrc, nints;
uint8_t ilr;
devs = sc->sc_serdevs;
if (sc->sc_ilr == PUC_ILR_DIGI) {
idx = 0;
while (devs & (0xfful << idx)) {
ilr = ~bus_read_1(sc->sc_port[idx].p_rres, 7);
devs &= ~0ul ^ ((u_long)ilr << idx);
idx += 8;
}
} else if (sc->sc_ilr == PUC_ILR_QUATECH) {
nints = 0;
while (1) {
/*
* Don't trust the value if it's the same as the option
* register. It may mean that the ILR is not active and
* we're reading the option register instead. This may
* lead to false positives on 8-port boards.
* Obtain the set of devices with pending interrupts.
*/
ilr = bus_read_1(sc->sc_port[0].p_rres, 7);
if (ilr != (sc->sc_cfg_data & 0xff))
devs &= (u_long)ilr;
}
ipend = 0;
idx = 0, dev = 1UL;
while (devs != 0UL) {
while ((devs & dev) == 0UL)
idx++, dev <<= 1;
devs &= ~dev;
port = &sc->sc_port[idx];
port->p_ipend = SERDEV_IPEND(port->p_dev);
ipend |= port->p_ipend;
}
i = 0, isrc = SER_INT_OVERRUN;
while (ipend) {
while (i < PUC_ISRCCNT && !(ipend & isrc))
i++, isrc <<= 1;
KASSERT(i < PUC_ISRCCNT, ("%s", __func__));
ipend &= ~isrc;
idx = 0, dev = 1UL;
devs = sc->sc_serdevs;
while (devs != 0UL) {
while ((devs & dev) == 0UL)
idx++, dev <<= 1;
devs &= ~dev;
port = &sc->sc_port[idx];
if (!(port->p_ipend & isrc))
continue;
if (port->p_ihsrc[i] != NULL)
(*port->p_ihsrc[i])(port->p_iharg);
if (sc->sc_ilr == PUC_ILR_DIGI) {
idx = 0;
while (devs & (0xfful << idx)) {
ilr = ~bus_read_1(sc->sc_port[idx].p_rres, 7);
devs &= ~0ul ^ ((u_long)ilr << idx);
idx += 8;
}
} else if (sc->sc_ilr == PUC_ILR_QUATECH) {
/*
* Don't trust the value if it's the same as the option
* register. It may mean that the ILR is not active and
* we're reading the option register instead. This may
* lead to false positives on 8-port boards.
*/
ilr = bus_read_1(sc->sc_port[0].p_rres, 7);
if (ilr != (sc->sc_cfg_data & 0xff))
devs &= (u_long)ilr;
}
if (devs == 0UL)
break;
/*
* Obtain the set of interrupt sources from those devices
* that have pending interrupts.
*/
ipend = 0;
idx = 0, dev = 1UL;
ds = devs;
while (ds != 0UL) {
while ((ds & dev) == 0UL)
idx++, dev <<= 1;
ds &= ~dev;
port = &sc->sc_port[idx];
port->p_ipend = SERDEV_IPEND(port->p_dev);
ipend |= port->p_ipend;
}
if (ipend == 0)
break;
i = 0, isrc = SER_INT_OVERRUN;
while (ipend) {
while (i < PUC_ISRCCNT && !(ipend & isrc))
i++, isrc <<= 1;
KASSERT(i < PUC_ISRCCNT, ("%s", __func__));
ipend &= ~isrc;
idx = 0, dev = 1UL;
ds = devs;
while (ds != 0UL) {
while ((ds & dev) == 0UL)
idx++, dev <<= 1;
ds &= ~dev;
port = &sc->sc_port[idx];
if (!(port->p_ipend & isrc))
continue;
if (port->p_ihsrc[i] != NULL)
(*port->p_ihsrc[i])(port->p_iharg);
nints++;
}
}
return (FILTER_HANDLED);
}
return (FILTER_STRAY);
return ((nints > 0) ? FILTER_HANDLED : FILTER_STRAY);
}
int