aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/puc
diff options
context:
space:
mode:
authorMarcel Moolenaar <marcel@FreeBSD.org>2009-12-11 03:08:07 +0000
committerMarcel Moolenaar <marcel@FreeBSD.org>2009-12-11 03:08:07 +0000
commit57467e59336cecb6278637ab741895d0844e1956 (patch)
tree3d02fafe38705864703f4d2fdb83077d4de636df /sys/dev/puc
parentae6de8d67af370f9a7d1d1cfcb843fa565390945 (diff)
downloadsrc-57467e59336cecb6278637ab741895d0844e1956.tar.gz
src-57467e59336cecb6278637ab741895d0844e1956.zip
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
Notes
Notes: svn path=/head/; revision=200397
Diffstat (limited to 'sys/dev/puc')
-rw-r--r--sys/dev/puc/puc.c108
1 files changed, 62 insertions, 46 deletions
diff --git a/sys/dev/puc/puc.c b/sys/dev/puc/puc.c
index 39e7a19a6111..b6fa3c5e5609 100644
--- a/sys/dev/puc/puc.c
+++ b/sys/dev/puc/puc.c
@@ -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;
- }
+ 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) {
+ /*
+ * 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;
- 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;
+ /*
+ * Obtain the set of interrupt sources from those devices
+ * that have pending interrupts.
+ */
+ ipend = 0;
idx = 0, dev = 1UL;
- devs = sc->sc_serdevs;
- while (devs != 0UL) {
- while ((devs & dev) == 0UL)
+ ds = devs;
+ while (ds != 0UL) {
+ while ((ds & dev) == 0UL)
idx++, dev <<= 1;
- devs &= ~dev;
+ 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);
+ 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