早期的內核版本中,幾乎所有的中斷都是由__do_IRQ函數進行處理,但是,因爲各種中斷請求的電氣特性會有所不同,又或者中斷控制器的特性也不同,這會導致以下這些處理也會有所不同:
- 何時對中斷控制器發出ack迴應;
- mask_irq和unmask_irq的處理;
- 中斷控制器是否需要eoi迴應?
- 何時打開cpu的本地irq中斷?以便允許irq的嵌套;
- 中斷數據結構的同步和保護;
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/
爲此,通用中斷子系統把幾種常用的流控類型進行了抽象,併爲它們實現了相應的標準函數,我們只要選擇相應的函數,賦值給irq所對應的irq_desc結構的handle_irq字段中即可。這些標準的回調函數都是irq_flow_handler_t類型:
點擊(此處)摺疊或打開
-
typedef void (*irq_flow_handler_t)(unsigned int irq,
- struct irq_desc *desc);
目前的通用中斷子系統實現了以下這些標準流控回調函數,這些函數都定義在:kernel/irq/chip.c中,
-
handle_simple_irq 用於簡易流控處理;
- handle_level_irq 用於電平觸發中斷的流控處理;
-
handle_edge_irq 用於邊沿觸發中斷的流控處理;
-
handle_fasteoi_irq 用於需要響應eoi的中斷控制器;
-
handle_percpu_irq 用於只在單一cpu響應的中斷;
- handle_nested_irq 用於處理使用線程的嵌套中斷;
驅動程序和板級代碼可以通過以下幾個API設置irq的流控函數:
-
irq_set_handler();
-
irq_set_chip_and_handler();
-
irq_set_chip_and_handler_name();
以下這個序列圖展示了整個通用中斷子系統的中斷響應過程,flow_handle一欄就是中斷流控層的生命週期:
圖1.1 通用中斷子系統的中斷響應過程
2. handle_simple_irq
該函數沒有實現任何實質性的流控操作,在把irq_desc結構鎖住後,直接調用handle_irq_event處理irq_desc中的action鏈表,它通常用於多路複用(類似於中斷控制器級聯)中的子中斷,由父中斷的流控回調中調用。或者用於無需進行硬件控制的中斷中。以下是它的經過簡化的代碼:
點擊(此處)摺疊或打開
-
void
-
handle_simple_irq(unsigned int irq, struct irq_desc *desc)
-
{
-
raw_spin_lock(&desc->lock);
-
......
-
handle_irq_event(desc);
-
-
out_unlock:
-
raw_spin_unlock(&desc->lock);
- }
點擊(此處)摺疊或打開
-
void
-
handle_level_irq(unsigned int irq, struct irq_desc *desc)
-
{
-
raw_spin_lock(&desc->lock);
-
mask_ack_irq(desc);
-
-
if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
-
goto out_unlock;
-
......
-
-
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
-
goto out_unlock;
-
-
handle_irq_event(desc);
-
-
if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))
-
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()。假設action->handler沒有屏蔽irq,以下這幅圖展示了電平中斷期間IRQ_PROGRESS標誌、本地中斷狀態和觸發其他CPU的狀態:
點擊(此處)摺疊或打開
-
if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
-
irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
-
if (!irq_check_poll(desc)) {
-
desc->istate |= IRQS_PENDING;
-
mask_ack_irq(desc);
-
goto out_unlock;
-
}
-
}
-
- desc->irq_data.chip->irq_ack(&desc->irq_data);
從上面的分析可以知道,處理中斷期間,另一次請求可能由另一個cpu響應後掛起,所以在處理完本次請求後還要判斷IRQS_PENDING標誌,如果被置位,當前cpu要接着處理被另一個cpu“委託”的請求。內核在這裏設置了一個循環來處理這種情況,直到IRQS_PENDING標誌無效爲止,而且因爲另一個cpu在響應並掛起irq時,會mask irq,所以在循環中要再次unmask irq,以便另一個cpu可以再次響應並掛起irq:
點擊(此處)摺疊或打開
-
do {
-
......
-
if (unlikely(desc->istate & IRQS_PENDING)) {
-
if (!irqd_irq_disabled(&desc->irq_data) &&
-
irqd_irq_masked(&desc->irq_data))
-
unmask_irq(desc);
-
}
-
-
handle_irq_event(desc);
-
-
} while ((desc->istate & IRQS_PENDING) &&
- !irqd_irq_disabled(&desc->irq_data));
IRQS_PENDING標誌會在handle_irq_event中清除。
點擊(此處)摺疊或打開
-
void
-
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
-
{
-
raw_spin_lock(&desc->lock);
-
-
if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
-
if (!irq_check_poll(desc))
-
goto out;
-
......
-
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
-
desc->istate |= IRQS_PENDING;
-
mask_irq(desc);
-
goto out;
-
}
-
-
if (desc->istate & IRQS_ONESHOT)
-
mask_irq(desc);
-
-
preflow_handler(desc);
-
handle_irq_event(desc);
-
-
out_eoi:
-
desc->irq_data.chip->irq_eoi(&desc->irq_data);
-
out_unlock:
-
raw_spin_unlock(&desc->lock);
-
return;
-
......
- }
點擊(此處)摺疊或打開
-
void
-
handle_percpu_irq(unsigned int irq, struct irq_desc *desc)
-
{
-
struct irq_chip *chip = irq_desc_get_chip(desc);
-
-
kstat_incr_irqs_this_cpu(irq, desc);
-
-
if (chip->irq_ack)
-
chip->irq_ack(&desc->irq_data);
-
-
handle_irq_event_percpu(desc, desc->action);
-
-
if (chip->irq_eoi)
-
chip->irq_eoi(&desc->irq_data);
- }
7. handle_nested_irq
該函數用於實現其中一種中斷共享機制,當多箇中斷共享某一根中斷線時,我們可以把這個中斷線作爲父中斷,共享該中斷的各個設備作爲子中斷,在父中斷的中斷線程中決定和分發響應哪個設備的請求,在得出真正發出請求的子設備後,調用handle_nested_irq來響應中斷。所以,該函數是在進程上下文執行的,我們也無需掃描和執行irq_desc結構中的action鏈表。父中斷在初始化時必須通過irq_set_nested_thread函數明確告知中斷子系統:這些子中斷屬於線程嵌套中斷類型,這樣驅動程序在申請這些子中斷時,內核不會爲它們建立自己的中斷線程,所有的子中斷共享父中斷的中斷線程。
點擊(此處)摺疊或打開
-
void handle_nested_irq(unsigned int irq)
-
{
-
......
-
might_sleep();
-
-
raw_spin_lock_irq(&desc->lock);
-
......
-
action = desc->action;
-
if (unlikely(!action || irqd_irq_disabled(&desc->irq_data)))
-
goto out_unlock;
-
-
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
-
raw_spin_unlock_irq(&desc->lock);
-
-
action_ret = action->thread_fn(action->irq, action->dev_id);
-
-
raw_spin_lock_irq(&desc->lock);
-
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
-
-
out_unlock:
-
raw_spin_unlock_irq(&desc->lock);
- }