中斷子系統5_電流層處理

//  中斷電流類型:
//      邊沿型(edge);
//          經過電位變化觸發中斷(上升沿/降低沿),若是外設但願觸發一箇中斷,它在irq line上發送一個脈衝,
//          而後釋放irq line恢復到inactive狀態。cpu經過檢測irq line上的脈衝來觸發中斷處理函數的執行。
//      電平型(level):
//          經過特定電位觸發中斷,若是外設但願觸發一箇中斷,它會將irq line設置到active level(高電平/低電平),
//          而後一直保持此irq line爲active level,直到中斷被處理。cpu經過週期性採樣irq line的電平來觸發中斷處理函數的執行。

//	在2.6.x版本後,中斷描述符irq_desc->handle_irq封裝對不一樣電流類型的處理。
//		struct irq_desc
//		{
//			irq_flow_handler_t handle_irq;
//			...
//		}

//  電流處理入口:
//      系統中全部中斷統一通過do_IRQ處理:
//          1.irq_desc提供電流處理例程(irq_desc->handle_irq != NULL),則調用;
//          2.不然,經過__do_IRQ處理全部電流類型。
//      do_IRQ(regs)
//      {   
//          ....
//          if(irq_desc->handle_irq)
//          {
//              irq_desc->handle_irq(irq, irq_desc);
//          }else
//          {
//              __do_IRQ(irq, regs);
//          }
//          ....
//      }
//


//	設置irq_desc的電流處理例程
//		參數:
//			typedef void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);
1.1 static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handler)
{
	struct irq_desc *desc;
	desc = irq_to_desc(irq);
	desc->handle_irq = handler;
}


//	邊沿型中斷處理
//  函數主要任務:
//      1.檢查中斷是否被禁用,或者中斷處理函數是否在運行過程當中
//          1.1 若是是,向芯片屏蔽並確認此中斷,設置標誌表示有待處理的中斷,退出
//      2.向芯片確認這次中斷
//      3.設置中斷處理函數在運行中標誌
//      4.運行中斷處理函數
//      5.檢查在運行中斷處理函數過程當中,是否有新中斷(步驟1中會被設置)
//          5.1 若是有,清除標誌,轉去步驟4
//      6.清除中斷處理函數在運行中標誌,表示全部已經發生的中斷都被處理完畢
//      7.退出
 2.1 void handle_edge_irq(unsigned int irq, struct irq_desc *desc)
 {
        const unsigned int cpu = smp_processor_id();
 
        spin_lock(&desc->lock);

        desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

        //只有設備的中斷請求引腳(中斷線)的電平發生跳變時(由高變低或者有低變高),纔會發出中斷請求,
        //由於跳變是一瞬間,並且不會像電平中斷能保持住電平,因此處理不當就特別容易漏掉一次中斷請求,
        //爲了不這種狀況,屏蔽中斷的時間必須越短越好。內核的開發者們顯然意識到這一點,在正是處理中斷前,
        //判斷IRQ_INPROGRESS標誌沒有被設置的狀況下,只是ack irq,並無mask irq,以便復位設備的中斷請求引腳,
        //在這以後的中斷處理期間,另外的cpu能夠再次響應同一個irq請求,若是IRQ_INPROGRESS已經置位,
        //代表另外一個CPU正在處理該irq的上一次請求,這種狀況下,他只是簡單地設置IRQ_PENDING標誌,而後mask_ack_irq後退出,
        //中斷請求交由原來的CPU繼續處理。由於是mask_ack_irq,因此係統實際上只容許掛起一次中斷。
        if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
                    !desc->action)) {
                desc->status |= (IRQ_PENDING | IRQ_MASKED);
                mask_ack_irq(desc, irq);
                goto out_unlock;
        }

        //向芯片確認此irq
        desc->chip->ack(irq);
 
        //中斷處理函數運行
        desc->status |= IRQ_INPROGRESS;
 
        do {
                struct irqaction *action = desc->action;
                irqreturn_t action_ret;

                //若是沒有中斷處理函數,屏蔽此中斷
                if (unlikely(!action)) {
                        desc->chip->mask(irq);
                        goto out_unlock;
                }
 
                //處理中斷期間,另外一次請求可能由另外一個cpu響應後掛起,因此在處理完本次請求後還要判斷IRQ_PENDING標誌,
                //若是被置位,當前cpu要接着處理被另外一個cpu「委託」的請求。內核在這裏設置了一個循環來處理這種狀況,
                //直到IRQ_PENDING標誌無效爲止,並且由於另外一個cpu在響應並掛起irq時,會mask irq,
                //因此在循環中要再次unmask irq,以便另外一個cpu能夠再次響應並掛起irq。
                if (unlikely((desc->status &
                               (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
                              (IRQ_PENDING | IRQ_MASKED))) {
                        //解除屏蔽,從新容許中斷
                        desc->chip->unmask(irq);
                        desc->status &= ~IRQ_MASKED;
                }

                //處理在運行中斷處理函數過程當中發生的中斷
                desc->status &= ~IRQ_PENDING;
                spin_unlock(&desc->lock);
                //調用中斷處理函數
                action_ret = handle_IRQ_event(irq, action);
                spin_lock(&desc->lock);
 
        } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
        //中斷所有被處理
        desc->status &= ~IRQ_INPROGRESS;
 out_unlock:
        spin_unlock(&desc->lock);
}

//  電平型中斷
//      只要設備的中斷請求引腳(中斷線)保持在預設的觸發電平,中斷就會一直被請求,
//        因此,爲了不同一中斷被重複響應,必須在處理中斷前先把mask irq,而後ack irq,
//      以便復位設備的中斷請求引腳,響應完成後再unmask irq。

//  函數主要任務:
//      1.向芯片屏蔽並確認此中斷
//      2.運行中斷處理函數
//      3.解除屏蔽
//      4.退出
2.2 void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);
        //確認並屏蔽此中斷
        mask_ack_irq(desc);

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
        
        //沒有中斷處理函數,或者中斷被禁止,退出
        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
                goto out_unlock;
        //調用中斷處理函數
        handle_irq_event(desc);
        //解除屏蔽
        cond_unmask_irq(desc);
out_unlock:
        raw_spin_unlock(&desc->lock);
}
//  雖然handle_level_irq對電平中斷的流控進行了必要的處理,由於電平中斷的特性:只要沒有ack irq,中斷線會一直有效,
//  因此咱們不會錯過某次中斷請求,可是驅動程序的開發人員若是對該過程理解不透徹,特別容易發生某次中斷被屢次處理的狀況。
//  特別是使用了中斷線程(action->thread_fn)來響應中斷的時候:一般mask_ack_irq只會清除中斷控制器的pending狀態,
//  不少慢速設備(例如經過i2c或spi控制的設備)須要在中斷線程中清除中斷線的pending狀態,可是未等到中斷線程被調度執行的時候,
//  handle_level_irq早就返回了,這時已經執行過unmask_irq,設備的中斷線pending處於有效狀態,中斷控制器會再次發出中斷請求,
//  結果是設備的一次中斷請求,產生了兩次中斷響應。要避免這種狀況,最好的辦法就是不要單獨使用中斷線程處理中斷,
//  而是要實現request_threaded_irq()的第二個參數irq_handler_t:handler,在handle回調中使用disable_irq()
//  關閉該irq,而後在退出中斷線程回調前再enable_irq()。




//  參考:
//      http://en.wikipedia.org/wiki/Interrupt
//      http://blog.csdn.net/xiaoxiaomuyu2010/article/details/12162599
//      http://blog.csdn.net/droidphone/article/details/7489756
相關文章
相關標籤/搜索