Port-arm archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[PATCH 6/8] ARM: fix IRQ reentrancy



- Fix the case when an IRQ with a higher IPL preempts another
  IRQ handler.

  Currently the blocked PIC mask is global and the blocked IRQ
  mask is per PIC, so that each pending PIC/IRQ is marked in
  these masks regardless of their IPL. After the higher IPL
  preempts the lower one and the corresponding handlers are run,
  processed but still unblocked lower IPL IRQs might be cleared
  from the blocked masks without actually getting unblocked, thus
  future IRQs will be lost.

  The patch changes the blocked PIC/IRQ masks into a per IPL
  array, so IRQs can be unblocked on a per IPL manner.

- Remove the unused irq_count from pic_deliver_irqs. This value
  is anyway the same as irq_base.

- Fix the case when an IRQ is triggered during the execution of
  another IRQ's handler on the same IPL / PIC. This could happen
  if the new IRQ is in a group that has already been processed
  in pic_deliver_irqs and not rechecked again.

  To fix this do the IPL unmarking (and if necessary that of the PIC)
  before IRQs are reenabled, so that any new IRQs on the same IPL / PIC
  will be marked correctly. Start over to check the first IRQ group
  if there has been any new IRQs during the previous round.

- Change the progress KASSERT to one which checks if the IPL was indeed
  unmarked as the result of processing this PIC / IPL.

- Don't recalculate pending IRQs in the delivery loop. There won't
  be any new one until IRQ unblocking, which happens only after the
  whole IPL is processed.

- Add a pending flag to interrupts sources so we can KASSERT on
  IRQ handlers not beeing reentered.

- In case of chained IRQ handlers, like the OMAP gpio IRQ,
  pic_handle_intr is called to mark pending interrupts on the
  chained PIC. This should be done with interrupts disabled,
  to protect global data like pic_pending_ipls.

- Remove extra whitespace.
---
 sys/arch/arm/pic/pic.c    |  109 ++++++++++++++++++++++++++------------------
 sys/arch/arm/pic/picvar.h |    3 +-
 2 files changed, 66 insertions(+), 46 deletions(-)

diff --git a/sys/arch/arm/pic/pic.c b/sys/arch/arm/pic/pic.c
index c8053a6..f0cf228 100644
--- a/sys/arch/arm/pic/pic.c
+++ b/sys/arch/arm/pic/pic.c
@@ -58,7 +58,7 @@ struct pic_softc *pic_list[PIC_MAXPICS];
 #if PIC_MAXPICS > 32
 #error PIC_MAXPICS > 32 not supported
 #endif
-volatile uint32_t pic_blocked_pics;
+volatile uint32_t pic_blocked_pics_by_ipl[NIPL];
 volatile uint32_t pic_pending_pics;
 volatile uint32_t pic_pending_ipls;
 volatile uint32_t pic_pending_ipl_refcnt[NIPL];
@@ -79,9 +79,12 @@ int
 pic_handle_intr(void *arg)
 {
        struct pic_softc * const pic = arg;
+       register_t psw;
        int rv;
 
+       psw = disable_interrupts(I32_bit);
        rv = (*pic->pic_ops->pic_find_pending_irqs)(pic);
+       restore_interrupts(psw);
 
        return rv > 0;
 }
@@ -122,7 +125,7 @@ pic_mark_pending_sources(struct pic_softc *pic, size_t 
irq_base,
                return ipl_mask;
 
        KASSERT((irq_base & 31) == 0);
-       
+
        (*pic->pic_ops->pic_block_irqs)(pic, irq_base, pending);
 
        *ipending |= pending;
@@ -182,28 +185,33 @@ pic_dispatch(struct intrsource *is, void *frame)
        is->is_ev.ev_count++;
 }
 
+static inline
+void pic_unmark_ipl(struct pic_softc *pic, uint32_t ipl_mask)
+{
+       KASSERT(pic->pic_pending_ipls & ipl_mask);
+       pic->pic_pending_ipls &= ~ipl_mask;
+       if (pic->pic_pending_ipls == 0)
+               pic_pending_pics &= ~__BIT(pic->pic_id);
+}
+
 void
 pic_deliver_irqs(struct pic_softc *pic, int ipl, void *frame)
 {
        const uint32_t ipl_mask = __BIT(ipl);
        struct intrsource *is;
        uint32_t *ipending = pic->pic_pending_irqs;
-       uint32_t *iblocked = pic->pic_blocked_irqs;
+       uint32_t *iblocked = &pic->pic_blocked_irqs_by_ipl[ipl][0];
        size_t irq_base;
-#if PIC_MAXSOURCES > 32
-       size_t irq_count;
-#endif
        uint32_t pending_irqs;
        uint32_t blocked_irqs;
        int irq;
-       bool progress = false;
-       
-       KASSERT(pic->pic_pending_ipls & ipl_mask);
 
        irq_base = 0;
-#if PIC_MAXSOURCES > 32
-       irq_count = 0;
-#endif
+
+       /* Do it this early, so that when we reenable interrupts for
+        * the handlers, new PICs / IPLs will be marked correctly.
+        */
+       pic_unmark_ipl(pic, ipl_mask);
 
        for (;;) {
                pending_irqs = pic_find_pending_irqs_by_ipl(pic, irq_base,
@@ -212,91 +220,102 @@ pic_deliver_irqs(struct pic_softc *pic, int ipl, void 
*frame)
                KASSERT((pending_irqs & ~(*ipending)) == 0);
                if (pending_irqs == 0) {
 #if PIC_MAXSOURCES > 32
-                       irq_count += 32;
-                       if (__predict_true(irq_count >= pic->pic_maxsources))
-                               break;
                        irq_base += 32;
-                       ipending++;
-                       iblocked++;
-                       if (irq_base >= pic->pic_maxsources) {
+                       if (__predict_false(irq_base >= pic->pic_maxsources)) {
+                               /* Now that we went through once all groups,
+                                * check for any new pending IRQs in the same
+                                * IPL and PIC. Note that this loop is bounded
+                                * since processed IRQs remain blocked, thus
+                                * we consider each of them only once in this
+                                * function.
+                                */
+                               if ((pic->pic_pending_ipls & ipl_mask) == 0)
+                                       break;
+                               pic_unmark_ipl(pic, ipl_mask);
+                               irq_base = 0;
                                ipending = pic->pic_pending_irqs;
-                               iblocked = pic->pic_blocked_irqs;
+                               iblocked =
+                                       &pic->pic_blocked_irqs_by_ipl[ipl][0];
+                       } else {
+                               ipending++;
+                               iblocked++;
                        }
-                       continue;
 #else
-                       break;
+                       if ((pic->pic_pending_ipls & ipl_mask) == 0)
+                               break;
+                       pic_unmark_ipl(pic, ipl_mask);
 #endif
+                       continue;
                }
-               progress = true;
                blocked_irqs = pending_irqs;
                do {
                        irq = ffs(pending_irqs) - 1;
                        KASSERT(irq >= 0);
 
                        *ipending &= ~__BIT(irq);
+                       pending_irqs &= ~__BIT(irq);
                        is = pic->pic_sources[irq_base + irq];
                        if (is != NULL) {
+                               KASSERT(!is->is_pending);
+                               is->is_pending = 1;
                                cpsie(I32_bit);
                                pic_dispatch(is, frame);
                                cpsid(I32_bit);
+                               is->is_pending = 0;
                        } else {
                                KASSERT(0);
                                blocked_irqs &= ~__BIT(irq);
                        }
-                       pending_irqs = pic_find_pending_irqs_by_ipl(pic,
-                           irq_base, *ipending, ipl);
                } while (pending_irqs);
                if (blocked_irqs) {
                        *iblocked |= blocked_irqs;
-                       pic_blocked_pics |= __BIT(pic->pic_id);
+                       pic_blocked_pics_by_ipl[ipl] |= __BIT(pic->pic_id);
                }
        }
 
-       KASSERT(progress);
-       /*
-        * Since interrupts are disabled, we don't have to be too careful
-        * about these.
-        */
-       pic->pic_pending_ipls &= ~ipl_mask;
-       if (pic->pic_pending_ipls == 0)
-               pic_pending_pics &= ~__BIT(pic->pic_id);
+       KASSERT((pic->pic_pending_ipls & ipl_mask) == 0);
 }
 
 static void
-pic_list_unblock_irqs(void)
+pic_list_unblock_irqs(int ipl)
 {
-       uint32_t blocked = pic_blocked_pics;
+       uint32_t blocked_pics = pic_blocked_pics_by_ipl[ipl];
 
-       pic_blocked_pics = 0;
+       pic_blocked_pics_by_ipl[ipl] = 0;
        for (;;) {
                struct pic_softc *pic;
+               int progress;
 #if PIC_MAXSOURCES > 32
                uint32_t *iblocked;
                size_t irq_base;
 #endif
 
-               int pic_id = ffs(blocked);
+               int pic_id = ffs(blocked_pics);
                if (pic_id-- == 0)
                        return;
 
                pic = pic_list[pic_id];
                KASSERT(pic != NULL);
+               progress = 0;
 #if PIC_MAXSOURCES > 32
-               for (irq_base = 0, iblocked = pic->pic_blocked_irqs;
+               for (irq_base = 0,
+                    iblocked = &pic->pic_blocked_irqs_by_ipl[ipl][0];
                     irq_base < pic->pic_maxsources;
                     irq_base += 32, iblocked++) {
                        if (*iblocked) {
                                (*pic->pic_ops->pic_unblock_irqs)(pic,
                                    irq_base, *iblocked);
                                *iblocked = 0;
+                               progress = 1;
                        }
                }
-               blocked &= ~__BIT(pic_id);
+               KASSERT(progress);
 #else
-               KASSERT(pic->pic_blocked_irqs[0] != 0);
+               KASSERT(pic->pic_blocked_irqs_by_ipl[ipl][0] != 0);
                (*pic->pic_ops->pic_unblock_irqs)(pic,
-                   0, pic->pic_blocked_irqs[0]);
+                   0, pic->pic_blocked_irqs_by_ipl[ipl][0]);
 #endif
+               blocked_pics &= ~__BIT(pic_id);
        }
 }
 
@@ -352,7 +371,7 @@ pic_do_pending_ints(register_t psw, int newipl, void *frame)
 
                        ci->ci_cpl = ipl;
                        pic_list_deliver_irqs(psw, ipl, frame);
-                       pic_list_unblock_irqs();
+                       pic_list_unblock_irqs(ipl);
                }
        }
 
@@ -440,7 +459,7 @@ pic_establish_intr(struct pic_softc *pic, int irq, int ipl, 
int type,
        is->is_type = type;
        is->is_func = func;
        is->is_arg = arg;
-       
+
        if (pic->pic_ops->pic_source_name)
                (*pic->pic_ops->pic_source_name)(pic, irq, is->is_source,
                    sizeof(is->is_source));
@@ -465,7 +484,7 @@ pic_establish_intr(struct pic_softc *pic, int irq, int ipl, 
int type,
 
        /*
         * Move up all the sources by one.
-        */
+        */
        if (ipl < NIPL) {
                off = pic_ipl_offset[ipl+1];
                memmove(&pic__iplsources[off+1], &pic__iplsources[off],
@@ -492,7 +511,7 @@ pic_establish_intr(struct pic_softc *pic, int irq, int ipl, 
int type,
 
        (*pic->pic_ops->pic_unblock_irqs)(pic, is->is_irq & ~0x1f,
            __BIT(is->is_irq & 0x1f));
-       
+
        /* We're done. */
        return is;
 }
diff --git a/sys/arch/arm/pic/picvar.h b/sys/arch/arm/pic/picvar.h
index 1cf1fab..f7b8aca 100644
--- a/sys/arch/arm/pic/picvar.h
+++ b/sys/arch/arm/pic/picvar.h
@@ -75,6 +75,7 @@ struct intrsource {
        uint8_t is_ipl;                         /* IPL_xxx */
        uint8_t is_irq;                         /* local to pic */
        uint8_t is_iplidx;
+       int     is_pending;
        struct evcnt is_ev;
        char is_source[16];
 };
@@ -83,7 +84,7 @@ struct pic_softc {
        const struct pic_ops *pic_ops;
        struct intrsource **pic_sources;
        uint32_t pic_pending_irqs[(PIC_MAXSOURCES + 31) / 32];
-       uint32_t pic_blocked_irqs[(PIC_MAXSOURCES + 31) / 32];
+       uint32_t pic_blocked_irqs_by_ipl[NIPL][(PIC_MAXSOURCES + 31) / 32];
        uint32_t pic_pending_ipls;
        size_t pic_maxsources;
        uint8_t pic_id;
-- 
1.5.6.56.g29b0d



Home | Main Index | Thread Index | Old Index