Linux kernel的中斷子系統之(四):High level irq event handler

返回目錄:《ARM-Linux中斷系統》。html

總結:從架構相關的彙編處理跳轉到Machine/控制器相關的handle_arch_irq,generic_handle_irq做爲High level irq event handler入口。linux

一介紹了進入High level irq event handler的路徑__irq_svc-->irq_handler-->handle_arch_irq,generic_handle_irq是入口函數,在generic_handle_irq_desc指向desc->handle_irq,這裏面就根據中斷類型不一樣採起不一樣的handler。數據結構

二介紹了IRQ自動探測、IRQ重發等機制。架構

三介紹了CPU和中斷控制器之間的接口,中斷控制器和外設之間的接口。併發

四接着一重點介紹了電平和邊沿觸發兩種類型中斷的High level irq event handler。app

 

原文地址:linux kernel的中斷子系統之(四):High level irq event handlerdom

 

1、前言異步

當外設觸發一次中斷後,一個大概的處理過程是:函數

一、具體CPU architecture相關的模塊會進行現場保護而後調用machine driver對應的中斷處理handlerpost

二、machine driver對應的中斷處理handler中會根據硬件的信息獲取HW interrupt ID,而且經過irq domain模塊翻譯成IRQ number

三、調用該IRQ number對應的high level irq event handler,在這個high level的handler中,會經過和interupt controller交互,進行中斷處理的flow control(處理中斷的嵌套、搶佔等),固然最終會遍歷該中斷描述符的IRQ action list調用外設的specific handler來處理該中斷

四、具體CPU architecture相關的模塊會進行現場恢復

上面的一、4這兩個步驟在linux kernel的中斷子系統之(六):ARM中斷處理過程中已經有了較爲細緻的描述,步驟2在linux kernel的中斷子系統之(二):irq domain介紹中介紹,本文主要描述步驟3,也就是linux中斷子系統的high level irq event handler。

注:這份文檔充滿了猜想和空想,不少地方描述多是有問題的,不過我仍是把它發出來,拋磚引玉,但願能夠引起你們討論。

1、如何進入high level irq event handler

一、從具體CPU architecture的中斷處理到machine相關的處理模塊

說到具體的CPU,咱們仍是用ARM爲例好了。對於ARM,咱們在ARM中斷處理文檔中已經有了較爲細緻的描述。這裏咱們看看如何從從具體CPU的中斷處理到machine相關的處理模塊 ,其具體代碼以下:

Notes:從中斷向量表中__irq_svc-->irq_handler-->handle_arch_irq,其中handle_arch_irq是由具體的中斷控制器實現。

    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

其實,直接從CPU的中斷處理跳轉到通用中斷處理模塊是不可能的,中斷處理不可能越過interrupt controller這個層次。通常而言,通用中斷處理模塊會提供一些通用的中斷代碼處理庫,而後由interrupt controller這個層次的代碼調用這些通用中斷處理的完成整個的中斷處理過程。「interrupt controller這個層次的代碼」是和硬件中斷系統設計相關的,例如:系統中有多少個interrupt contrller,每一個interrupt controller是如何控制的?它們是如何級聯的?咱們稱這些相關的驅動模塊爲machine interrupt driver。

在上面的代碼中,若是配置了MULTI_IRQ_HANDLER的話,ARM中斷處理則直接跳轉到一個叫作handle_arch_irq函數,若是系統中只有一個類型的interrupt controller(多是多個interrupt controller,例如使用兩個級聯的GIC),那麼handle_arch_irq能夠在interrupt controller初始化的時候設定。代碼以下:

……

if (gic_nr == 0) {
        set_handle_irq(gic_handle_irq);
}

……

gic_nr是GIC的編號,linux kernel初始化過程當中,每發現一個GIC,都是會指向GIC driver的初始化函數的,不過對於第一個GIC,gic_nr等於0,對於第二個GIC,gic_nr等於1。固然handle_arch_irq這個函數指針不是per CPU的變量,是所有CPU共享的,所以,初始化一次就OK了。

當使用多種類型的interrupt controller的時候(例如HW 系統使用了S3C2451這樣的SOC,這時候,系統有兩種interrupt controller,一種是GPIO type,另一種是SOC上的interrupt controller),則不適合在interrupt controller中進行設定,這時候,能夠考慮在machine driver中設定。在這種狀況下,handle_arch_irq 這個函數是在setup_arch函數中根據machine driver設定,具體以下:

handle_arch_irq = mdesc->handle_irq;

關於MULTI_IRQ_HANDLER這個配置項,咱們能夠再多說幾句。固然,其實這個配置項的名字已經出賣它了。multi irq handler就是說系統中有多個irq handler,能夠在run time的時候指定。爲什麼要run time的時候,從多個handler中選擇一個呢?HW interrupt block難道不是固定的嗎?個人理解(猜測)是:一個kernel的image支持多個HW platform,對於不一樣的HW platform,在運行時檢查HW platform的類型,設定不一樣的irq handler。

二、interrupt controller相關的代碼

咱們仍是以2個級聯的GIC爲例來描述interrupt controller相關的代碼。代碼以下:

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];-------------------------------獲取root GIC的硬件描述符
    void __iomem *cpu_base = gic_data_cpu_base(gic); --------------獲取root GIC mapping到CPU地址空間的信息

    do {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);------獲取HW interrupt ID
        irqnr = irqstat & ~0x1c00;

        if (likely(irqnr > 15 && irqnr < 1021)) {-------------------------------SPI和PPI的處理
            irqnr = irq_find_mapping(gic->domain, irqnr);------------------將HW interrupt ID轉成IRQ number
            handle_IRQ(irqnr, regs);---------------------------------------------處理該IRQ number
            continue;
        }
        if (irqnr < 16) {------------------------------------------------------------IPI類型的中斷處理
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

更多關於GIC相關的信息,請參考linux kernel的中斷子系統之(七):GIC代碼分析。對於ARM處理器,handle_IRQ代碼以下:

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{

……
        generic_handle_irq(irq);

……
}

三、調用high level handler

調用high level handler的代碼邏輯很是簡單,以下:

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq); ----------經過IRQ number獲取該irq的描述符

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);----------------調用high level的irq handler來處理該IRQ
    return 0;
}

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);---------------------------這裏面的handle_irq指向哪裏呢?
}

Notes:根據不一樣的中斷號,handle_irq是不一樣的。在irq_domain_ops的map中處理。

const struct irq_domain_ops gic_irq_domain_ops = {
    .map = gic_irq_domain_map,
    .xlate = gic_irq_domain_xlate,
};

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                irq_hw_number_t hw)
{
    if (hw < 32) {--------------------------------------------小於32的中斷HW id
        irq_set_percpu_devid(irq);
        irq_set_chip_and_handler(irq, &gic_chip,--------------設置irq對應的irq_desc成員handle_irq爲handle_percpu_devid_irq
                     handle_percpu_devid_irq);
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
    } else {--------------------------------------------------大於等於32的HW id
        irq_set_chip_and_handler(irq, &gic_chip,--------------設置irq對應的irq_desc成員handle_irq爲handle_fasteoi_irq,
                     handle_fasteoi_irq);
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
    }
    irq_set_chip_data(irq, d->host_data);
    return 0;
}

 

其中handle_fasteoi_irq-->handle_irq_event-->handle_irq_event_percpu,下面看看handle_percpu_devid_irq和handle_irq_event_percpu。

有irq號找到對應的irq_desc,irq_desc->action->handler就是對應中斷號的處理函數。

void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);
    struct irqaction *action = desc->action;
    void *dev_id = __this_cpu_ptr(action->percpu_dev_id);
    irqreturn_t res;

    kstat_incr_irqs_this_cpu(irq, desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    trace_irq_handler_entry(irq, action);
 res = action->handler(irq, dev_id);     trace_irq_handler_exit(irq, action, res);

    if (chip->irq_eoi)
        chip->irq_eoi(&desc->irq_data);
}


irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;

    do {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
 res = action->handler(irq, action->dev_id);         trace_irq_handler_exit(irq, action, res);
...
return retval;
}

 

咱們在註冊函數的時候使用setup_irq,有兩個參數一個是IRQ number,另外一個是irqaction。

    ret=setup_irq(AP_TIMER4_INT, &at4_irq);


static struct irqaction at4_irq = 
{
    .name        = "at4_irq",
    .flags        = IRQF_DISABLED | IRQF_TRIGGER_RISING, 
    .handler    = at4_isr,-------------------------------------對應的中斷處理函數。
};

 

這樣子就將從HW id到IRQ number,而後經過中斷控制器驅動,再到經過中斷模塊,最後調用到對應中斷的處理函數。

 

2、理解high level irq event handler須要的知識準備

一、自動探測IRQ

一個硬件驅動能夠經過下面的方法進行自動探測它使用的IRQ:

unsigned long irqs;
int irq;

irqs = probe_irq_on();--------啓動IRQ自動探測
驅動那個打算自動探測IRQ的硬件產生中斷
irq = probe_irq_off(irqs);-------結束IRQ自動探測

若是可以自動探測到IRQ,上面程序中的irq(probe_irq_off的返回值)就是自動探測的結果。後續程序能夠經過request_threaded_irq申請該IRQ。probe_irq_on函數主要的目的是返回一個32 bit的掩碼,經過該掩碼能夠知道可能使用的IRQ number有哪些,具體代碼以下:

unsigned long probe_irq_on(void)
{

……
    for_each_irq_desc_reverse(i, desc) { ----scan 從nr_irqs-1 到0 的中斷描述符
        raw_spin_lock_irq(&desc->lock);
        if (!desc->action && irq_settings_can_probe(desc)) {--------(1)
            desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;-----(2)
            if (irq_startup(desc, false))
                desc->istate |= IRQS_PENDING;
        }
        raw_spin_unlock_irq(&desc->lock);
    }
    msleep(100); --------------------------(3)

    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {------------(4)
            if (!(desc->istate & IRQS_WAITING)) {
                desc->istate &= ~IRQS_AUTODETECT;
                irq_shutdown(desc);
            } else
                if (i < 32)------------------------(5)
                    mask |= 1 << i;
        }
        raw_spin_unlock_irq(&desc->lock);
    }

    return mask;
}

(1)那些能自動探測IRQ的中斷描述符須要具體兩個條件:

a、該中斷描述符尚未經過request_threaded_irq或者其餘方式申請該IRQ的specific handler(也就是irqaction數據結構)

b、該中斷描述符容許自動探測(不能設定IRQ_NOPROBE)

(2)若是知足上面的條件,那麼該中斷描述符屬於備選描述符。設定其internal state爲IRQS_AUTODETECT | IRQS_WAITING。IRQS_AUTODETECT表示本IRQ正處於自動探測中。

(3)在等待過程當中,系統仍然容許,各類中斷依然會觸發。在各類high level irq event handler中,總會有以下的代碼:

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

這裏會清除IRQS_WAITING狀態。

(4)這時候,咱們尚未控制那個想要自動探測IRQ的硬件產生中斷,所以處於自動探測中,而且IRQS_WAITING並清除的必定不是咱們期待的IRQ(多是spurious interrupts致使的),這時候,clear IRQS_AUTODETECT,shutdown該IRQ。

(5)最大探測的IRQ是31(mask是一個32 bit的value),mask返回的是可能的irq掩碼。

咱們再來看看probe_irq_off的代碼:

int probe_irq_off(unsigned long val)
{
    int i, irq_found = 0, nr_of_irqs = 0;
    struct irq_desc *desc;

    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {----只有處於IRQ自動探測中的描述符纔會被處理
            if (!(desc->istate & IRQS_WAITING)) {----找到一個潛在的中斷描述符
                if (!nr_of_irqs)
                    irq_found = i;
                nr_of_irqs++;
            }
            desc->istate &= ~IRQS_AUTODETECT; ----IRQS_WAITING沒有被清除,說明該描述符
            irq_shutdown(desc);                                     不是自動探測的那個,shutdown之
        }
        raw_spin_unlock_irq(&desc->lock);
    }
    mutex_unlock(&probing_active);

    if (nr_of_irqs > 1) ------若是找到多於1個的IRQ,說明探測失敗,返回負的IRQ個數信息
        irq_found = -irq_found;

    return irq_found;
}

由於在調用probe_irq_off已經觸發了自動探測IRQ的那個硬件中斷,所以在該中斷的high level handler的執行過程當中,該硬件對應的中斷描述符的IRQS_WAITING標緻應該已經被清除,所以probe_irq_off函數scan中斷描述符DB,找處處於auto probe中,並且IRQS_WAITING標緻被清除的那個IRQ。若是找到一個,那麼探測OK,返回該IRQ number,若是找到多個,說明探測失敗,返回負的IRQ個數信息,沒有找到的話,返回0。

二、resend一箇中斷

一個ARM SOC老是有不少的GPIO,有些GPIO能夠提供中斷功能,這些GPIO的中斷能夠配置成level trigger或者edge trigger。通常而言,你們都更喜歡用level trigger的中斷。有的SOC只能是有限個數的GPIO能夠配置成電平中斷,所以,在項目初期進行pin define的時候,你們都在爭搶電平觸發的GPIO。

電平觸發的中斷有什麼好處呢?電平觸發的中斷很簡單、直接,只要硬件檢測到硬件事件(例若有數據到來),其assert指定的電平信號,CPU ack該中斷後,電平信號消失。可是對於邊緣觸發的中斷,它是用一個上升沿或者降低沿告知硬件的狀態,這個狀態不是一個持續的狀態,若是軟件處理很差,容易丟失中斷。

何時會resend一箇中斷呢?咱們考慮一個簡單的例子:

(1)CPU A上正在處理x外設的中斷

(2)x外設的中斷再次到來(CPU A已經ack該IRQ,所以x外設的中斷能夠再次觸發),這時候其餘CPU會處理它(mask and ack),並設置該中斷描述符是pending狀態,並委託CPU A處理該pending狀態的中斷。須要注意的是CPU已經ack了該中斷,所以該中斷的硬件狀態已經不是pending狀態,沒法觸發中斷了,這裏的pending狀態是指中斷描述符的軟件狀態。

(3)CPU B上因爲同步的需求,disable了x外設的IRQ,這時候,CPU A沒有處理pending狀態的x外設中斷就離開了中斷處理過程。

(4)當enable x外設的IRQ的時候,須要檢測pending狀態以便resend該中斷,不然,該中斷會丟失的

具體代碼以下:

void check_irq_resend(struct irq_desc *desc, unsigned int irq)
{
    if (irq_settings_is_level(desc)) {-------電平中斷不存在resend的問題
        desc->istate &= ~IRQS_PENDING;
        return;
    }
    if (desc->istate & IRQS_REPLAY)----若是已經設定resend的flag,退出就OK了,這個應該
        return;                                                和irq的enable disable能多層嵌套相關
    if (desc->istate & IRQS_PENDING) {-------若是有pending的flag則進行處理
        desc->istate &= ~IRQS_PENDING;
        desc->istate |= IRQS_REPLAY; ------設置retrigger標誌

        if (!desc->irq_data.chip->irq_retrigger ||
            !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) {----調用底層irq chip的callback
#ifdef CONFIG_HARDIRQS_SW_RESEND
也可使用軟件手段來完成resend一箇中斷,具體代碼省略,有興趣你們能夠本身看看
#endif
        }
    }
}

在各類high level irq event handler中,總會有以下的代碼:

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

這裏會清除IRQS_REPLAY狀態,表示該中斷已經被retrigger,一次resend interrupt的過程結束。

三、unhandled interrupt和spurious interrupt

在中斷處理的最後,總會有一段代碼以下:

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{

……

    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

note_interrupt就是進行unhandled interrupt和spurious interrupt處理的。對於這類中斷,linux kernel有一套複雜的機制來處理,你能夠經過command line參數(noirqdebug)來控制開關該功能。

當發生了一箇中斷,可是沒有被處理(有兩種可能,一種是根本沒有註冊的specific handler,第二種是有handler,可是handler否定是本身對應的設備觸發的中斷),怎麼辦?毫無疑問這是一個異常情況,那麼kernel是否要馬上採起措施將該IRQ disable呢?也不太合適,畢竟interrupt request信號線是容許共享的,直接disable該IRQ有可能會下手太狠,kernel採起了這樣的策略:若是該IRQ觸發了100,000次,可是99,900次沒有處理,在這種條件下,咱們就是disable這個interrupt request line。多麼有情有義的策略啊!相關的控制數據在中斷描述符中,以下:

struct irq_desc {
……
    unsigned int        irq_count;--------記錄發生的中斷的次數,每100,000則回滾
    unsigned long        last_unhandled;-----上一次沒有處理的IRQ的時間點
    unsigned int        irqs_unhandled;------沒有處理的次數
……
}

irq_count和irqs_unhandled都是比較直觀的,爲什麼要記錄unhandled interrupt發生的時間呢?咱們來看具體的代碼。具體的相關代碼位於note_interrupt中,以下:

void note_interrupt(unsigned int irq, struct irq_desc *desc,  irqreturn_t action_ret)
{
    if (desc->istate & IRQS_POLL_INPROGRESS ||  irq_settings_is_polled(desc))
        return;

    if (action_ret == IRQ_WAKE_THREAD)----handler返回IRQ_WAKE_THREAD是正常狀況
        return;

    if (bad_action_ret(action_ret)) {-----報告錯誤,這些是因爲specific handler的返回錯誤致使的
        report_bad_irq(irq, desc, action_ret);
        return;
    }

    if (unlikely(action_ret == IRQ_NONE)) {-------是unhandled interrupt
        if (time_after(jiffies, desc->last_unhandled + HZ/10))---(1)
            desc->irqs_unhandled = 1;---從新開始計數
        else
            desc->irqs_unhandled++;---斷定爲unhandled interrupt,計數加一
        desc->last_unhandled = jiffies;-------保存本次unhandled interrupt對應的jiffies時間
    }

if (unlikely(try_misrouted_irq(irq, desc, action_ret))) {---是否啓動Misrouted IRQ fixup
    int ok = misrouted_irq(irq);
    if (action_ret == IRQ_NONE)
        desc->irqs_unhandled -= ok;
}

    desc->irq_count++;
    if (likely(desc->irq_count < 100000))-----------(2)
        return;

    desc->irq_count = 0;
    if (unlikely(desc->irqs_unhandled > 99900)) {--------(3)
        __report_bad_irq(irq, desc, action_ret);---報告錯誤
        desc->istate |= IRQS_SPURIOUS_DISABLED;
        desc->depth++;
        irq_disable(desc);

        mod_timer(&poll_spurious_irq_timer,----------(4)
              jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
    }
    desc->irqs_unhandled = 0;
}

(1)是不是一次有效的unhandled interrupt還要根據時間來判斷。通常而言,當硬件處於異常狀態的時候每每是很是短的時間觸發很是屢次的中斷,若是距離上次unhandled interrupt的時間超過了10個jiffies(若是HZ=100,那麼時間就是100ms),那麼咱們要把irqs_unhandled從新計數。若是不這麼處理的話,隨着時間的累計,最終irqs_unhandled可能會達到99900次的,從而把這個IRQ錯誤的推上了審判臺。

(2)irq_count每次都會加一,記錄IRQ被觸發的次數。但只要大於100000才啓動 step (3)中的檢查。一旦啓動檢查,irq_count會清零,irqs_unhandled也會清零,進入下一個檢查週期。

(3)若是知足條件(IRQ觸發了100,000次,可是99,900次沒有處理),disable該IRQ。

(4)啓動timer,輪詢整個系統中的handler來處理這個中斷(輪詢啊,絕對是真愛啊)。這個timer的callback函數定義以下:

static void poll_spurious_irqs(unsigned long dummy)
{
    struct irq_desc *desc;
    int i;

    if (atomic_inc_return(&irq_poll_active) != 1)----確保系統中只有一個excuting thread進入臨界區
        goto out;
    irq_poll_cpu = smp_processor_id(); ----記錄當前正在polling的CPU

    for_each_irq_desc(i, desc) {------遍歷全部的中斷描述符
        unsigned int state;

        if (!i)-------------越過0號中斷描述符。對於X86,這是timer的中斷
             continue;

        /* Racy but it doesn't matter */
        state = desc->istate;
        barrier();
        if (!(state & IRQS_SPURIOUS_DISABLED))----名花有主的那些就沒必要考慮了
            continue;

        local_irq_disable();
        try_one_irq(i, desc, true);---------OK,嘗試一下是否是能夠處理
        local_irq_enable();
    }
out:
    atomic_dec(&irq_poll_active);
    mod_timer(&poll_spurious_irq_timer,--------一旦觸發了該timer,就停不下來
          jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
}

3、和high level irq event handler相關的硬件描述

一、CPU layer和Interrupt controller之間的接口

從邏輯層面上看,CPU和interrupt controller之間的接口包括:

(1)觸發中斷的signal。通常而言,這個(些)信號是電平觸發的。對於ARM CPU,它是nIRQ和nFIQ信號線,對於X86,它是INT和NMI信號線,對於PowerPC,這些信號線包括MC(machine check)、CRIT(critical interrupt)和NON-CRIT(Non critical interrupt)。對於linux kernel的中斷子系統,咱們只使用其中一個信號線(例如對於ARM而言,咱們只使用nIRQ這個信號線)。這樣,從CPU層面看,其邏輯動做很是的簡單,不區分優先級,觸發中斷的那個信號線一旦assert,而且CPU沒有mask中斷,那麼軟件就會轉到一個異常向量執行,完畢後返回現場

(2)Ack中斷的signal。這個signal多是物理上的一個鏈接CPU和interrupt controller的銅線,也可能不是。對於X86+8259這樣的結構,Ack中斷的signal就是nINTA信號線,對於ARM+GIC而言,這個信號就是總線上的一次訪問(讀Interrupt Acknowledge Register寄存器)。CPU ack中斷標識cpu開啓啓動中斷服務程序(specific handler)去處理該中斷。對於X86而言,ack中斷可讓8259將interrupt vector數據送到數據總線上,從而讓CPU獲取了足夠的處理該中斷的信息。對於ARM而言,ack中斷的同時也就是獲取了發生中斷的HW interrupt ID,總而言之,ack中斷後,CPU獲取了足夠開啓執行中斷處理的信息。

(3)結束中斷(EOI,end of interrupt)的signal。這個signal用來標識CPU已經完成了對該中斷的處理(specific handler或者ISR,interrupt serivce routine執行完畢)。實際的物理形態這裏就不描述了,和ack中斷signal是相似的。

(4)控制總線和數據總線接口。經過這些接口,CPU能夠訪問(讀寫)interrupt controller的寄存器。

二、Interrupt controller和Peripheral device之間的接口

全部的系統中,Interrupt controller和Peripheral device之間的接口都是一個Interrupt Request信號線。外設經過這個信號線上的電平或者邊緣向CPU(其實是經過interrupt controller)申請中斷服務。

4、幾種典型的high level irq event handler

本章主要介紹幾種典型的high level irq event handler,在進行high level irq event handler的設定的時候須要注意,不是外設使用電平觸發就選用handle_level_irq,選用什麼樣的high level irq event handler是和Interrupt controller的行爲以及外設電平觸發方式決定的。介紹每一個典型的handler以前,我會簡單的描述該handler要求的硬件行爲,若是該外設的中斷系統符合這個硬件行爲,那麼能夠選擇該handler爲該中斷的high level irq event handler。

Notes:不一樣類型的觸發方式體如今handle_irq,經過irq_set_chip_and_handler註冊。有handle_edge_irq、handle_level_irq、handle_simple_irq、handle_percpu_devid_irq、handle_fasteoi_irq。   

註冊接口有以下,最接近中斷處理函數的是handle_irq_event_percpu和handle_percpu_devid_irq。

1.handle_edge_irq-->
2.handle_level_irq-->
3.handle_simple_irq-->
4.handle_fasteoi_irq-->
    handle_irq_event-->
        handle_irq_event_percpu
5.handle_percpu_devid_irq

 

一、邊緣觸發的handler。

使用handle_edge_irq這個handler的硬件中斷系統行爲以下:

xyz

咱們以上升沿爲例描述邊緣中斷的處理過程(降低沿的觸發是相似的)。當interrupt controller檢測到了上升沿信號,會將該上升沿狀態(pending)鎖存在寄存器中,並經過中斷的signal向CPU觸發中斷。須要注意:這時候,外設和interrupt controller之間的interrupt request信號線會保持高電平,這也就意味着interrupt controller不可能檢測到新的中斷信號(自己是高電平,沒法造成上升沿)。這個高電平信號會一直保持到軟件ack該中斷(調用irq chip的irq_ack callback函數)。ack以後,中斷控制器纔有可能繼續探測上升沿,觸發下一次中斷

ARM+GIC組成的系統不符合這個類型。雖然GIC提供了IAR(Interrupt Acknowledge Register)寄存器來讓ARM來ack中斷,可是,在調用high level handler以前,中斷處理程序須要經過讀取IAR寄存器得到HW interrpt ID並轉換成IRQ number,所以實際上,對於GIC的irq chip,它是沒法提供本場景中的irq_ack函數的。不少GPIO type的interrupt controller符合上面的條件,它們會提供pending狀態寄存器,讀能夠獲取pending狀態,而向pending狀態寄存器寫1能夠ack該中斷,讓interrupt controller能夠繼續觸發下一次中斷。

handle_edge_irq代碼以下:

void handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock); -----------------(0)

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);----參考上一章的描述
    if (unlikely(irqd_irq_disabled(&desc->irq_data) ||-----------(1)
             irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
        if (!irq_check_poll(desc)) {
            desc->istate |= IRQS_PENDING;
            mask_ack_irq(desc);
            goto out_unlock;
        }
    }
    kstat_incr_irqs_this_cpu(irq, desc); ---更新該IRQ統計信息

    desc->irq_data.chip->irq_ack(&desc->irq_data); ---------(2)

    do {
        if (unlikely(!desc->action)) { -----------------(3)
            mask_irq(desc);
            goto out_unlock;
        }

        if (unlikely(desc->istate & IRQS_PENDING)) { ---------(4)
            if (!irqd_irq_disabled(&desc->irq_data) &&
                irqd_irq_masked(&desc->irq_data))
                unmask_irq(desc);
        }

        handle_irq_event(desc); -------------------(5)

    } while ((desc->istate & IRQS_PENDING) &&
         !irqd_irq_disabled(&desc->irq_data)); -------------(6)

out_unlock:
    raw_spin_unlock(&desc->lock); -----------------(7)
}

(0) 這時候,中斷仍然是關閉的,所以不會有來自本CPU的併發,使用raw spin lock就防止其餘CPU上對該IRQ的中斷描述符的訪問。針對該spin lock,咱們直觀的感受是raw_spin_lock和(7)中的raw_spin_unlock是成對的,實際上並非,handle_irq_event中的代碼是這樣的:

irqreturn_t handle_irq_event(struct irq_desc *desc)
{

    raw_spin_unlock(&desc->lock); -------和上面的(0)對應

    處理具體的action list

    raw_spin_lock(&desc->lock);--------和上面的(7)對應
}

實際上,因爲在handle_irq_event中處理action list的耗時仍是比較長的,所以處理具體的action list的時候並無持有中斷描述符的spin lock。在若是那樣的話,其餘CPU在對中斷描述符進行操做的時候須要spin的時間會很長的。

(1)判斷是否須要執行下面的action list的處理。這裏分紅幾種狀況:

a、該中斷事件已經被其餘的CPU處理了

b、該中斷被其餘的CPU disable了

c、該中斷描述符沒有註冊specific handler。這個比較簡單,若是沒有irqaction,根本沒有必要調用action list的處理

若是該中斷事件已經被其餘的CPU處理了,那麼咱們僅僅是設定pending狀態(爲了委託正在處理的該中斷的那個CPU進行處理),mask_ack_irq該中斷並退出就OK了,並不作具體的處理。另外正在處理該中斷的CPU會檢查pending狀態,並進行處理的。一樣的,若是該中斷被其餘的CPU disable了,本就不該該繼續執行該中斷的specific handler,咱們也是設定pending狀態,mask and ack中斷就退出了。當其餘CPU的代碼離開臨界區,enable 該中斷的時候,軟件會檢測pending狀態並resend該中斷。

這裏的irq_check_poll代碼以下:

static bool irq_check_poll(struct irq_desc *desc)
{
    if (!(desc->istate & IRQS_POLL_INPROGRESS))
        return false;
    return irq_wait_for_poll(desc);
}

IRQS_POLL_INPROGRESS標識了該IRQ正在被polling(上一章有描述),若是沒有被輪詢,那麼返回false,進行正常的設定pending標記、mask and ack中斷。若是正在被輪詢,那麼須要等待poll結束。

(2)ack該中斷。對於中斷控制器,一旦被ack,表示該外設的中斷被enable,硬件上已經準備好觸發下一次中斷了。再次觸發的中斷會被調度到其餘的CPU上。如今,咱們能夠再次回到步驟(1)中,爲何這裏用mask and ack而不是單純的ack呢?若是單純的ack則意味着後續中斷仍是會觸發,這時候怎麼處理?在pending+in progress的狀況下,咱們要怎麼處理?記錄pending的次數,有意義嗎?因爲中斷是徹底異步的,也有可能pending的標記可能在另外的CPU上已經修改成replay的標記,這時候怎麼辦?當事情變得複雜的時候,那必定是原本方向就錯了,所以,mask and ack就是最好的策略,我已經記錄了pending狀態,再也不考慮pending嵌套的狀況。

(3)在調用specific handler處理具體的中斷的時候,因爲不持有中斷描述符的spin lock,所以其餘CPU上有可能會註銷其specific handler,所以do while循環以後,desc->action有多是NULL,若是是這樣,那麼mask irq,而後退出就OK了

(4)若是中斷描述符處於pending狀態,那麼必定是其餘CPU上又觸發了該interrupt source的中斷,並設定了pending狀態,「委託」本CPU進行處理,這時候,需要把以前mask住的中斷進行unmask的操做一旦unmask了該interrupt source,後續的中斷能夠繼續觸發,由其餘的CPU處理(仍然是設定中斷描述符的pending狀態,委託當前正在處理該中斷請求的那個CPU進行處理)。

(5)處理該中斷請求事件

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;----CPU已經準備處理該中斷了,所以,清除pending狀態
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);--設定INPROGRESS的flag
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action); ---遍歷action list,調用specific handler

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);---處理完成,清除INPROGRESS標記
    return ret;
}

(6)只要有pending標記,就說明該中斷還在pending狀態,須要繼續處理。固然,若是有其餘的CPU disable了該interrupt source,那麼本次中斷結束處理。

二、電平觸發的handler

使用handle_level_irq這個handler的硬件中斷系統行爲以下:

level

咱們以高電平觸發爲例。當interrupt controller檢測到了高電平信號,並經過中斷的signal向CPU觸發中斷。這時候,對中斷控制器進行ack並不能改變interrupt request signal上的電平狀態一直要等到執行具體的中斷服務程序(specific handler),對外設進行ack的時候,電平信號纔會恢復成低電平在對外設ack以前,中斷狀態一直是pending的,若是沒有mask中斷,那麼中斷控制器就會assert CPU

handle_level_irq的代碼以下:

void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
    mask_ack_irq(desc); ---------------------(1)

    if (unlikely(irqd_irq_inprogress(&desc->irq_data)))---------(2)
        if (!irq_check_poll(desc))
            goto out_unlock;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);--和retrigger中斷以及自動探測IRQ相關
    kstat_incr_irqs_this_cpu(irq, desc);

    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {-----(3)
        desc->istate |= IRQS_PENDING;
        goto out_unlock;
    }

    handle_irq_event(desc);

    cond_unmask_irq(desc); --------------(4)

out_unlock:
    raw_spin_unlock(&desc->lock);
}

(1)考慮CPU<------>interrupt controller<------>device這樣的鏈接方式中,咱們認爲high level handler主要是和interrupt controller交互,而specific handler(request_irq註冊的那個)是和device進行交互。Level類型的中斷的特色就是只要外設interrupt request line的電平狀態是有效狀態,對於interrupt controller,該外設的interrupt老是active的。因爲外設檢測到了事件(好比數據到來了),所以assert了指定的電平信號,這個電平信號會一直保持,直到軟件清除了外設的狀態寄存器。可是,high level irq event handler這個層面只能操做Interrupt controller,不能操做具體外設的寄存器(那應該屬於具體外設的specific interrupt handler處理內容,該handler會掛入中斷描述符中的IRQ action list)。直到在具體的中斷服務程序(specific handler中)操做具體外設的寄存器,才能讓這個asserted電平信號消息

正是由於level trigger的這個特色,所以,在high level handler中首先mask並ack該IRQ。這一點和邊緣觸發的high level handler有顯著的不一樣,在handle_edge_irq中,咱們僅僅是ack了中斷,並無mask,由於邊緣觸發的中斷稍縱即逝,一旦mask了該中斷,容易形成中斷丟失。而對於電平中斷,咱們不得不mask住該中斷,若是不mask住,只要CPU ack中斷,中斷控制器將持續的assert CPU中斷(由於有效電平狀態一直保持)。若是咱們mask住該中斷,中斷控制器將再也不轉發該interrupt source來的中斷,所以,全部的CPU都不會感知到該中斷,直到軟件unmask。這裏的ack是針對interrupt controller的ack,自己ack就是爲了clear interrupt controller對該IRQ的狀態寄存器,不過因爲外部的電平仍然是有效信號,其實未必能清除interrupt controller的中斷狀態,不過這是和中斷控制器硬件實現相關的。

(2)對於電平觸發的high level handler,咱們一開始就mask並ack了中斷,所以後續specific handler因該是串行化執行的,爲什麼要判斷in progress標記呢?不要忘記spurious interrupt,那裏會直接調用handler來處理spurious interrupt。

(3)這裏有兩個場景

a、沒有註冊specific handler。若是沒有註冊handler,那麼保持mask並設定pending標記(這個pending標記有什麼做用尚未想明白)。

b、該中斷被其餘的CPU disable了。若是該中斷被其餘的CPU disable了,本就不該該繼續執行該中斷的specific handler,咱們也是設定pending狀態,mask and ack中斷就退出了。當其餘CPU的代碼離開臨界區,enable 該中斷的時候,軟件會檢測pending狀態並resend該中斷。

(4)爲什麼是有條件的unmask該IRQ?正常的話固然是umask就OK了,不過有些threaded interrupt(這個概念在下一份文檔中描述)要求是one shot的(首次中斷,specific handler中開了一槍,wakeup了irq handler thread,若是容許中斷嵌套,那麼在specific handler會屢次開槍,這也就不是one shot了,有些IRQ的handler thread要求是one shot,也就是不能嵌套specific handler)。

三、支持EOI的handler

TODO

原創文章,轉發請註明出處。蝸窩科技。http://www.wowotech.net/linux_kenrel/High_level_irq_event_handler.html

相關文章
相關標籤/搜索