當發生中斷以後,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應用開發徹底手冊》