summaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorKanoj Sarcar <kanoj@engr.sgi.com>2000-05-13 07:16:24 +0000
committerKanoj Sarcar <kanoj@engr.sgi.com>2000-05-13 07:16:24 +0000
commit6cf9b4f61029a3495200d1e1b0b64ab58022ca14 (patch)
tree77e991b84162718f027b9412bdfe7994c72f0eb8 /arch
parent35fd5eb6a16239d684d509f74380ddef0f59194b (diff)
Fix races in the low level intr handling code to prevent intr loss
and double servicing of intrs. Read checked in comments for more details.
Diffstat (limited to 'arch')
-rw-r--r--arch/mips64/sgi-ip27/ip27-irq.c42
1 files changed, 28 insertions, 14 deletions
diff --git a/arch/mips64/sgi-ip27/ip27-irq.c b/arch/mips64/sgi-ip27/ip27-irq.c
index 2c0ddcc69..50feeed72 100644
--- a/arch/mips64/sgi-ip27/ip27-irq.c
+++ b/arch/mips64/sgi-ip27/ip27-irq.c
@@ -170,8 +170,18 @@ static int ms1bit(unsigned long x)
return b + (int) (x >> 1);
}
-
-/* For now ... */
+
+/*
+ * This code is unnecessarily complex, because we do SA_INTERRUPT
+ * intr enabling. Basically, once we grab the set of intrs we need
+ * to service, we must mask _all_ these interrupts; firstly, to make
+ * sure the same intr does not intr again, causing recursion that
+ * can lead to stack overflow. Secondly, we can not just mask the
+ * one intr we are do_IRQing, because the non-masked intrs in the
+ * first set might intr again, causing multiple servicings of the
+ * same intr. This effect is mostly seen for intercpu intrs.
+ * Kanoj 05.13.00
+ */
void ip27_do_irq(struct pt_regs *regs)
{
int irq, swlevel;
@@ -182,19 +192,23 @@ void ip27_do_irq(struct pt_regs *regs)
/* copied from Irix intpend0() */
while (((pend0 = LOCAL_HUB_L(PI_INT_PEND0)) &
(mask0 = LOCAL_HUB_L(pi_int_mask0))) != 0) {
- pend0 &= mask0;
- do {
- swlevel = ms1bit(pend0);
- LOCAL_HUB_S(pi_int_mask0, mask0 & ~(1 << swlevel));
- LOCAL_HUB_CLR_INTR(swlevel);
- /* "map" swlevel to irq */
- irq = SWLEVEL_TO_IRQ(swlevel);
- do_IRQ(irq, regs);
- /* reset INT_MASK0 register */
+ pend0 &= mask0; /* Pick intrs we should look at */
+ if (pend0) {
+ /* Prevent any of the picked intrs from recursing */
+ LOCAL_HUB_S(pi_int_mask0, mask0 & ~(pend0));
+ do {
+ swlevel = ms1bit(pend0);
+ LOCAL_HUB_CLR_INTR(swlevel);
+ /* "map" swlevel to irq */
+ irq = SWLEVEL_TO_IRQ(swlevel);
+ do_IRQ(irq, regs);
+ /* clear bit in pend0 */
+ pend0 ^= 1ULL << swlevel;
+ } while(pend0);
+ /* Now allow the set of serviced intrs again */
LOCAL_HUB_S(pi_int_mask0, mask0);
- /* clear bit in pend0 */
- pend0 ^= 1ULL << swlevel;
- } while (pend0);
+ LOCAL_HUB_L(PI_INT_PEND0);
+ }
}
}