linux驅動之中斷處理過程C程序部分

      當發生中斷以後,linux系統在彙編階段通過一系列跳轉,最終跳轉到asm_do_IRQ()函數,開始C程序階段的處理。在彙編階段,程序已經計算出發生中斷的中斷號irq,這個關鍵參數最終傳遞給asm_do_IRQ()。linux驅動中斷處理C程序部分,主要涉及linux中斷系統數據結構的初始化和C程序的具體執行跳轉。html

1、中斷處理數據結構node

      linux內核將全部的中斷統一編號,使用一個irq_desc[NR_IRQS]的結構體數組來描述這些中斷:每一個數組項對應着一箇中斷源(多是一箇中斷,也多是一組中斷),記錄了中斷的入口處理函數(不是用戶註冊的處理函數)、中斷標記,並提供了中斷的底層硬件訪問函數(中斷清除、屏蔽、使能)。另外,經過這個結構體數組項成員action,可以找到用戶註冊的中斷處理函數。結構體irq_desc的數據類型在include/linux/irq.h中定義,內容以下:linux

struct irq_desc {
    irq_flow_handler_t    handle_irq;   /* 當前中斷的處理函數入口 */
    struct irq_chip        *chip;       /* 低層的硬件訪問 */
    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;        /* 用戶提供的中斷處理函數鏈表 */
    unsigned int        status;         /* IRQ狀態 */

    unsigned int        depth;          /* nested irq disables */
    unsigned int        wake_depth;     /* nested wake enables */
    unsigned int        irq_count;      /* For detecting broken IRQs */
    unsigned int        irqs_unhandled;
    spinlock_t        lock;
    const char        *name;            /* 中斷名稱 */
} ____cacheline_internodealigned_in_smp;

      irq_desc成員變量handle_irq是這個或這組中斷的入口處理函數,成員變量chip結構體包含了這個中斷的清除、屏蔽或者使能等底層函數,結構體類型irq_chip的定義也在include/linux/irq.h中,內容以下:數組

struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);
    void        (*shutdown)(unsigned int irq);
    void        (*enable)(unsigned int irq);
    void        (*disable)(unsigned int irq);

    void        (*ack)(unsigned int irq);
    void        (*mask)(unsigned int irq);
    void        (*mask_ack)(unsigned int irq);
    void        (*unmask)(unsigned int irq);
    const char    *typename;
};

      irq_desc成員變量action記錄了用戶註冊的中斷處理函數、中斷標誌等等內容,其類型irqaction類型定義在include/linux/interrupt.h中,內容以下:數據結構

struct irqaction {
    irq_handler_t handler;       /* 用戶註冊的中斷處理函數 */
    unsigned long flags;         /* 中斷標誌,是否共享中斷,電平觸發仍是邊沿觸發等 */
    cpumask_t mask;              /* 用於SMP */
    const char *name;            /* 用戶註冊的中斷名字,/proc/interrupts */
    void *dev_id;                /* 用戶傳遞給handler的參數,還能夠用來區分共享中斷 */
    struct irqaction *next;      /* 指向下一個irqaciton結構體指針 */
    int irq;                     /* 中斷號 */
    struct proc_dir_entry *dir;
};

      用戶註冊的每一箇中斷處理函數對應一個irqaciton結構體,一箇中斷源能夠有多個處理函數(共享終端),它們的irqaciton結構體能夠構成一個單項鍊表,irq_desc[irqn].action則是表頭。irq_desc[NR_IRQS]結構體數組的構成狀況以下圖所示:架構

 

      中斷的處理流程以下:函數

(1) 發生中斷後,CPU執行異常向量vector_irq的代碼;spa

(2)在vector_irq裏面,最終會調用中斷處理C程序總入口函數asm_do_IRQ();操作系統

(3)asm_do_IRQ()根據中斷號調用irq_des[NR_IRQS]數組中的對應數組項中的handle_irq();.net

(4)handle_irq()會使用chip的成員函數來設置硬件,例如清除中斷,禁止中斷,從新開啓中斷等;

(5)handle_irq逐個調用用戶在action鏈表中註冊的處理函數。

      可見,中斷體系結構的初始化,就是構造irq_desc[NR_IRQS]這個數據結構;用戶註冊中斷就是構造action鏈表;用戶卸載中斷就是從action鏈表中去除對應的項。

2、中斷處理系統(數據結構)初始化

一、操做系統相關中斷初始化

      init_IRQ()函數用來初始化中斷體系結構,代碼在arch/arm/kernel/irq.c中,代碼以下:

void __init init_IRQ(void)
{
    int irq;

    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    init_arch_irq();
}

      init_arch_irq()函數,就是用來初始化irq_desc[NR_IRQS]的,並且這個函數是與硬件平臺緊密相關的,是一個函數指針。在移植linux內核的時候,將它指向硬件平臺相關的中斷初始化函數。這裏咱們以S3C2440硬件平臺爲例,這個函數指針指向s3c24xx_init_irq()。

      s3c24xx_init_irq()函數在arch/arm/plat-s3c24xx/irq.c中定義,它爲全部的中斷設置了芯片相關的數據結構irq_desc[irq].chip,設置了處理函數入口irq_desc[irq].handle_irq。之外部中斷EINT0爲例,用來設置它們的代碼以下:

void __init s3c24xx_init_irq(void)
{
    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
}

      set_irq_chip()函數的做用就是「irq_desc[irqno].chip = & s3c_irq_eint0t4」。s3c_irq_eint0t4爲系統提供了一套操做EIN0~EINT4的中斷底層的函數集合,內容以下:

static struct irq_chip s3c_irq_eint0t4 = {
    .name        = "s3c-ext0",
    .ack        = s3c_irq_ack,
    .mask        = s3c_irq_mask,
    .unmask        = s3c_irq_unmask,
    .set_wake    = s3c_irq_wake,
    .set_type    = s3c_irqext_type,
};

      中斷處理函數入口爲handle_edge_irq(),也及時「irq_desc[irqno].handle_irq = handle_edge_irq」.發生中斷後,do_asm_irq()函數會調用該中斷入口處理函數handle_edge_irq(),而該函數會調用用戶註冊的具體處理函數。

二、用戶註冊中斷時帶來的中斷初始化

      用戶(驅動程序)經過request_irq()函數向內核註冊中斷處理函數,request_irq()函數根據中斷號找到數組irq_desc[irqno]對應的數組項,而後在它的action鏈表中添加一個action表項。,該函數在kernel/irq/manage.c中定義,函數內容以下:

int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
{
    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
    ..........
    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;
    ...........
    retval = setup_irq(irq, action);
}

      request_irq()函數首先使用4個參數構造一個irqaction結構,而後調用setup_irq()函數將它鏈入鏈表中,代碼以下:

int setup_irq(unsigned int irq, struct irqaction *new)
{
    /* 判斷是否沒有註冊過,若是已經註冊了就判斷是不是可共享的中斷 */
    p = &desc->action;
    old = *p;
    if (old) {
        if (!((old->flags & new->flags) & IRQF_SHARED) ||
            ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
            old_name = old->name;
            goto mismatch;
        }

        /* add new interrupt at end of irq queue */
        do {
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;
    }

    /* 鏈入新表項 */
    *p = new;
    
    /* 若是在鏈入以前不是空鏈,那麼以前的共享中斷已經設置了中斷觸發方式,沒有必要重複設置 */
    /* 若是鏈入以前是空鏈,那麼就須要設置中斷觸發方式 */
    if (!shared) {
        irq_chip_set_defaults(desc->chip);

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            if (desc->chip && desc->chip->set_type)
                desc->chip->set_type(irq,
                        new->flags & IRQF_TRIGGER_MASK);
            else
                printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                       "function for IRQ %d (%s)\n", irq,
                       desc->chip ? desc->chip->name :
                       "unknown");
        } else
            compat_irq_chip_set_default_handler(desc);

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

        if (!(desc->status & IRQ_NOAUTOEN)) {
            desc->depth = 0;
            desc->status &= ~IRQ_DISABLED;
            /* 啓動中斷 */
            if (desc->chip->startup)
                desc->chip->startup(irq);
            else
                desc->chip->enable(irq);
        } else
            /* Undo nested disables: */
            desc->depth = 1;
    }
    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;

    new->irq = irq;
    register_irq_proc(irq);
    new->dir = NULL;
    register_handler_proc(irq, new);
}

      setup_irq()函數主要完成的功能以下:

(1)將新建的irqaciton結構鏈入irq_desc[irq]結構體的action鏈表中;

① 若是action鏈表爲空,則直接鏈入;

② 若是非空,則要判斷新建的irqaciton結構和鏈表中的irqaciton結構所表示的中斷類型是否一致:便是都聲明爲「可共享的」,是否都是用相同的觸發方式,若是一致,則將新建的irqaciton結構鏈入;

(2)設置中斷的觸發方式;

(3)啓動中斷。

三、卸載中斷

      卸載中斷使用函數free_irq(),該函數定義在kernel/irq/manage.c中,須要用到兩個參數irq、dev_id。經過這參數irq能夠定位到action鏈表,再使用dev_id在鏈表中找到要卸載的表項(共享中斷的狀況)。若是它是惟一表項,那麼刪除中斷,還須要調用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()來關閉中斷,代碼的主要內容以下:

void free_irq(unsigned int irq, void *dev_id)
{
    desc = irq_desc + irq;
    p = &desc->action;
    for (;;) {
        struct irqaction *action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            if (action->dev_id != dev_id)
                continue;

            /* Found it - now remove it from the list of entries */
            *pp = action->next;

            if (!desc->action) {
                desc->status |= IRQ_DISABLED;
                if (desc->chip->shutdown)
                    desc->chip->shutdown(irq);
                else
                    desc->chip->disable(irq);
            }

            unregister_handler_proc(irq, action);
            kfree(action);
            return;
        }
        return;
    }
}

3、中斷的處理過程  

      從asm_do_IRQ()函數開始,分析linux系統中斷的處理流程,它在arch/arm/kernel/irq.c中定義,內容以下:

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc = irq_desc + irq;

    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();

    desc_handle_irq(irq, desc); /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}

      其中desc_handle_irq(irq,desc)是一個內聯函數調用,內聯函數展開的結果,就是irq_desc[irq].handle_irq(irq,desc)。內容以下:

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

一、普通流程(之外部中斷EINT0爲例)

      之外部中斷EINT0爲例分析程序的執行流程。經過對中斷系統數據結構初始化的瞭解,咱們知道irq_desc[IRQ_EINT0].handle_irq函數指針指向handle_edge_irq(),該函數在kernel/irq/chip.c中被定義,用來處理邊沿觸發的中斷,內容以下:

void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    kstat_cpu(cpu).irqs[irq]++;

    /* Start handling the irq */
    desc->chip->ack(irq);

    /* Mark the IRQ currently in progress.*/
    desc->status |= IRQ_INPROGRESS;

    action_ret = handle_IRQ_event(irq, action);
}

      經過函數調用desc->chip->ack(irq)來響應中斷,實際上就是清除中斷以使得能夠接受下一個中斷,有了以前數據結構初始化的前提了解,能夠知道實際上執行的就是s3c_irq_eint0t4.ack函數。handle_IRQ_event函數逐個執行action鏈表中用戶註冊的中斷處理函數,它在kernel/irq/handle.c中定義,關鍵代碼以下:

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    do {
        ret = action->handler(irq, action->dev_id);
        if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
        action = action->next;
    } while (action);
}

      用戶經過函數request_irq()函數註冊中斷處理函數時候,傳入參數irq和dev_id,在這裏這兩個參數被用戶註冊的中斷處理函數action->handler()所使用。可見用戶能夠在註冊中斷處理函數的時候,指定參數dev_id,而後未來再由註冊的中斷處理函數使用這個參數。

二、特殊流程(之外部中斷EINT5爲例)

      在S3C2440處理器架構中,EINT5中斷屬於EINT4t7中斷集合,是一個子中斷。當EINT5中斷線發生中斷事件,那麼將先跳轉到EINT4t7中斷號對應的中斷入口處理函數,也便是irq_desc[EINT4t7].handle_irq(irq,desc),進行具體子中斷肯定,而後再跳轉到真正發生中斷的中斷入口處理函數執行。回顧一下EINT5這個中斷註冊時的函數調用,以下:

request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, "S2", NULL);

      咱們並無在註冊EINT5中斷處理函數的時候,一併註冊了EINT4t7的處理函數,其實咱們也不可能使用IRQ_EINT4t7來註冊用戶中斷,由於咱們只會使用一箇中斷源。中斷集合EINT4t7的中斷入口處理函數,是在arch/arm/plat-s3c24xx/irq.c中的函數s3c24xx_init_irq()來初始化的,內容以下:

set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);

     因而可知,函數s3c_irq_demux_extint4t7()就是EINT4t7的中斷入口處理函數。因此,當發生EINT5中斷事件,那麼彙編階段根據INTOFFSET肯定中斷號爲IRQ_EINT4t7,asm_do_IRQ函數經過傳入的這個參數,將跳轉到irq_desc[EINT4t7].handle_irq(irq,desc)函數執行,也就是函數s3c_irq_demux_extint4t7(irq,desc),該函數的主要內容以下:

static void
s3c_irq_demux_extint4t7(unsigned int irq,
            struct irq_desc *desc)
{
    unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
    unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

    eintpnd &= ~eintmsk;
    eintpnd &= 0xff;    /* only lower irqs */

    while (eintpnd) {
        irq = __ffs(eintpnd);
        eintpnd &= ~(1<<irq);
        irq += (IRQ_EINT4 - 4);
        desc_handle_irq(irq, irq_desc + irq);
    }
}

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

     函數s3c_irq_demux_extint4t7()的做用,就是根據寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK從新計算中斷號,這個時候將計算出真正的中斷號IRQ_EINT5,而後經過desc_handle_irq(irq, irq_desc + irq)來調用irq_desc[EINT5].handle_irq(irq,desc)。此後的過程與EINT0發生中斷後的執行過程相似。

      可見,因爲S3C2440在設計的時候最多容許32箇中斷源(剩餘的中斷源採用子中斷的方式),因此彙編階段根據INTOFFSET肯定的中斷號取值範圍爲IRQ_EINT0~IRQ_EINT0+31。也就是說asm_do_IRQ函數的參數irq的取值範圍只有32個值,發生中斷多是一個實際的中斷號,也多是一組中斷的中斷號。若是是一個實際的中斷號,那麼直接跳轉到該中斷號對應irq_desc結構體下的handle_irq()執行便可;若是是一組中斷的中斷號,那麼將經過這組中斷對應的irq_desc結構體下的handle_irq()先來肯定實際發生中斷的中斷號,而後再來執行其中斷入口處理函數。

 

參考資料: 《嵌入式Linux應用開發徹底手冊》

                linux-2.6.26內核中ARM中斷實現詳解(轉)

相關文章
相關標籤/搜索