當(dāng)發(fā)生中斷之后,linux系統(tǒng)在匯編階段經(jīng)過一系列跳轉(zhuǎn),最終跳轉(zhuǎn)到asm_do_IRQ()函數(shù),開始C程序階段的處理。在匯編階段,程序已經(jīng)計算出發(fā)生中斷的中斷號irq,這個關(guān)鍵參數(shù)最終傳遞給asm_do_IRQ()。linux驅(qū)動中斷處理C程序部分,主要涉及l(fā)inux中斷系統(tǒng)數(shù)據(jù)結(jié)構(gòu)的初始化和C程序的具體執(zhí)行跳轉(zhuǎn)。
一、中斷處理數(shù)據(jù)結(jié)構(gòu)
linux內(nèi)核將所有的中斷統(tǒng)一編號,使用一個irq_desc[NR_IRQS]的結(jié)構(gòu)體數(shù)組來描述這些中斷:每個數(shù)組項對應(yīng)著一個中斷源(可能是一個中斷,也可能是一組中斷),記錄了中斷的入口處理函數(shù)(不是用戶注冊的處理函數(shù))、中斷標(biāo)記,并提供了中斷的底層硬件訪問函數(shù)(中斷清除、屏蔽、使能)。另外,通過這個結(jié)構(gòu)體數(shù)組項成員action,能夠找到用戶注冊的中斷處理函數(shù)。結(jié)構(gòu)體irq_desc的數(shù)據(jù)類型在include/linux/irq.h中定義,內(nèi)容如下:
struct irq_desc { irq_flow_handler_t handle_irq; /* 當(dāng)前中斷的處理函數(shù)入口 */ struct irq_chip *chip; /* 低層的硬件訪問 */ struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* 用戶提供的中斷處理函數(shù)鏈表 */ unsigned int status; /* IRQ狀態(tài) */ 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是這個或這組中斷的入口處理函數(shù),成員變量chip結(jié)構(gòu)體包含了這個中斷的清除、屏蔽或者使能等底層函數(shù),結(jié)構(gòu)體類型irq_chip的定義也在include/linux/irq.h中,內(nèi)容如下:
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記錄了用戶注冊的中斷處理函數(shù)、中斷標(biāo)志等等內(nèi)容,其類型irqaction類型定義在include/linux/interrupt.h中,內(nèi)容如下:
struct irqaction { irq_handler_t handler; /* 用戶注冊的中斷處理函數(shù) */ unsigned long flags; /* 中斷標(biāo)志,是否共享中斷,電平觸發(fā)還是邊沿觸發(fā)等 */ cpumask_t mask; /* 用于SMP */ const char *name; /* 用戶注冊的中斷名字,/proc/interrupts */ void *dev_id; /* 用戶傳遞給handler的參數(shù),還可以用來區(qū)分共享中斷 */ struct irqaction *next; /* 指向下一個irqaciton結(jié)構(gòu)體指針 */ int irq; /* 中斷號 */ struct proc_dir_entry *dir;};
用戶注冊的每個中斷處理函數(shù)對應(yīng)一個irqaciton結(jié)構(gòu)體,一個中斷源可以有多個處理函數(shù)(共享終端),它們的irqaciton結(jié)構(gòu)體可以構(gòu)成一個單項鏈表,irq_desc[irqn].action則是表頭。irq_desc[NR_IRQS]結(jié)構(gòu)體數(shù)組的構(gòu)成情況如下圖所示:
中斷的處理流程如下:
(1) 發(fā)生中斷后,CPU執(zhí)行異常向量vector_irq的代碼;
(2)在vector_irq里面,最終會調(diào)用中斷處理C程序總?cè)肟诤瘮?shù)asm_do_IRQ();
(3)asm_do_IRQ()根據(jù)中斷號調(diào)用irq_des[NR_IRQS]數(shù)組中的對應(yīng)數(shù)組項中的handle_irq();
(4)handle_irq()會使用chip的成員函數(shù)來設(shè)置硬件,例如清除中斷,禁止中斷,重新開啟中斷等;
(5)handle_irq逐個調(diào)用用戶在action鏈表中注冊的處理函數(shù)。
可見,中斷體系結(jié)構(gòu)的初始化,就是構(gòu)造irq_desc[NR_IRQS]這個數(shù)據(jù)結(jié)構(gòu);用戶注冊中斷就是構(gòu)造action鏈表;用戶卸載中斷就是從action鏈表中去除對應(yīng)的項。
二、中斷處理系統(tǒng)(數(shù)據(jù)結(jié)構(gòu))初始化
1、操作系統(tǒng)相關(guān)中斷初始化
init_IRQ()函數(shù)用來初始化中斷體系結(jié)構(gòu),代碼在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()函數(shù),就是用來初始化irq_desc[NR_IRQS]的,而且這個函數(shù)是與硬件平臺緊密相關(guān)的,是一個函數(shù)指針。在移植linux內(nèi)核的時候,將它指向硬件平臺相關(guān)的中斷初始化函數(shù)。這里我們以S3C2440硬件平臺為例,這個函數(shù)指針指向s3c24xx_init_irq()。
s3c24xx_init_irq()函數(shù)在arch/arm/plat-s3c24xx/irq.c中定義,它為所有的中斷設(shè)置了芯片相關(guān)的數(shù)據(jù)結(jié)構(gòu)irq_desc[irq].chip,設(shè)置了處理函數(shù)入口irq_desc[irq].handle_irq。以外部中斷EINT0為例,用來設(shè)置它們的代碼如下:
void __init s3c24xx_init_irq(void){ for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { irqdbf("registering irq %d (ext int) ", irqno); set_irq_chip(irqno, &s3c_irq_eint0t4); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); }}
set_irq_chip()函數(shù)的作用就是“irq_desc[irqno].chip = & s3c_irq_eint0t4”。s3c_irq_eint0t4為系統(tǒng)提供了一套操作EIN0~EINT4的中斷底層的函數(shù)集合,內(nèi)容如下:
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,};
中斷處理函數(shù)入口為handle_edge_irq(),也及時“irq_desc[irqno].handle_irq = handle_edge_irq”.發(fā)生中斷后,do_asm_irq()函數(shù)會調(diào)用該中斷入口處理函數(shù)handle_edge_irq(),而該函數(shù)會調(diào)用用戶注冊的具體處理函數(shù)。
2、用戶注冊中斷時帶來的中斷初始化
用戶(驅(qū)動程序)通過request_irq()函數(shù)向內(nèi)核注冊中斷處理函數(shù),request_irq()函數(shù)根據(jù)中斷號找到數(shù)組irq_desc[irqno]對應(yīng)的數(shù)組項,然后在它的action鏈表中添加一個action表項。,該函數(shù)在kernel/irq/manage.c中定義,函數(shù)內(nèi)容如下:
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()函數(shù)首先使用4個參數(shù)構(gòu)造一個irqaction結(jié)構(gòu),然后調(diào)用setup_irq()函數(shù)將它鏈入鏈表中,代碼如下:
int setup_irq(unsigned int irq, struct irqaction *new){ /* 判斷是否沒有注冊過,如果已經(jīng)注冊了就判斷是否是可共享的中斷 */ 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; /* 如果在鏈入之前不是空鏈,那么之前的共享中斷已經(jīng)設(shè)置了中斷觸發(fā)方式,沒有必要重復(fù)設(shè)置 */ /* 如果鏈入之前是空鏈,那么就需要設(shè)置中斷觸發(fā)方式 */ 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) ", 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()函數(shù)主要完成的功能如下:
(1)將新建的irqaciton結(jié)構(gòu)鏈入irq_desc[irq]結(jié)構(gòu)體的action鏈表中;
① 如果action鏈表為空,則直接鏈入;
② 如果非空,則要判斷新建的irqaciton結(jié)構(gòu)和鏈表中的irqaciton結(jié)構(gòu)所表示的中斷類型是否一致:即是都聲明為“可共享的”,是否都是用相同的觸發(fā)方式,如果一致,則將新建的irqaciton結(jié)構(gòu)鏈入;
(2)設(shè)置中斷的觸發(fā)方式;
(3)啟動中斷。
3、卸載中斷
卸載中斷使用函數(shù)free_irq(),該函數(shù)定義在kernel/irq/manage.c中,需要用到兩個參數(shù)irq、dev_id。通過這參數(shù)irq可以定位到action鏈表,再使用dev_id在鏈表中找到要卸載的表項(共享中斷的情況)。如果它是唯一表項,那么刪除中斷,還需要調(diào)用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()來關(guān)閉中斷,代碼的主要內(nèi)容如下:
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; }}
三、中斷的處理過程??
從asm_do_IRQ()函數(shù)開始,分析linux系統(tǒng)中斷的處理流程,它在arch/arm/kernel/irq.c中定義,內(nèi)容如下:
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)是一個內(nèi)聯(lián)函數(shù)調(diào)用,內(nèi)聯(lián)函數(shù)展開的結(jié)果,就是irq_desc[irq].handle_irq(irq,desc)。內(nèi)容如下:
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc){ desc->handle_irq(irq, desc);}
1、普通流程(以外部中斷EINT0為例)
以外部中斷EINT0為例分析程序的執(zhí)行流程。通過對中斷系統(tǒng)數(shù)據(jù)結(jié)構(gòu)初始化的了解,我們知道irq_desc[IRQ_EINT0].handle_irq函數(shù)指針指向handle_edge_irq(),該函數(shù)在kernel/irq/chip.c中被定義,用來處理邊沿觸發(fā)的中斷,內(nèi)容如下:
void fastcallhandle_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);}
通過函數(shù)調(diào)用desc->chip->ack(irq)來響應(yīng)中斷,實際上就是清除中斷以使得可以接受下一個中斷,有了之前數(shù)據(jù)結(jié)構(gòu)初始化的前提了解,可以知道實際上執(zhí)行的就是s3c_irq_eint0t4.ack函數(shù)。handle_IRQ_event函數(shù)逐個執(zhí)行action鏈表中用戶注冊的中斷處理函數(shù),它在kernel/irq/handle.c中定義,關(guān)鍵代碼如下:
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);}
用戶通過函數(shù)request_irq()函數(shù)注冊中斷處理函數(shù)時候,傳入?yún)?shù)irq和dev_id,在這里這兩個參數(shù)被用戶注冊的中斷處理函數(shù)action->handler()所使用??梢娪脩艨梢栽谧灾袛嗵幚砗瘮?shù)的時候,指定參數(shù)dev_id,然后將來再由注冊的中斷處理函數(shù)使用這個參數(shù)。
2、特殊流程(以外部中斷EINT5為例)
在S3C2440處理器架構(gòu)中,EINT5中斷屬于EINT4t7中斷集合,是一個子中斷。當(dāng)EINT5中斷線發(fā)生中斷事件,那么將先跳轉(zhuǎn)到EINT4t7中斷號對應(yīng)的中斷入口處理函數(shù),也即是irq_desc[EINT4t7].handle_irq(irq,desc),進(jìn)行具體子中斷確定,然后再跳轉(zhuǎn)到真正發(fā)生中斷的中斷入口處理函數(shù)執(zhí)行?;仡櫼幌翬INT5這個中斷注冊時的函數(shù)調(diào)用,如下:
request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, "S2", NULL);
我們并沒有在注冊EINT5中斷處理函數(shù)的時候,一并注冊了EINT4t7的處理函數(shù),其實我們也不可能使用IRQ_EINT4t7來注冊用戶中斷,因為我們只會使用一個中斷源。中斷集合EINT4t7的中斷入口處理函數(shù),是在arch/arm/plat-s3c24xx/irq.c中的函數(shù)s3c24xx_init_irq()來初始化的,內(nèi)容如下:
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
由此可見,函數(shù)s3c_irq_demux_extint4t7()就是EINT4t7的中斷入口處理函數(shù)。所以,當(dāng)發(fā)生EINT5中斷事件,那么匯編階段根據(jù)INTOFFSET確定中斷號為IRQ_EINT4t7,asm_do_IRQ函數(shù)通過傳入的這個參數(shù),將跳轉(zhuǎn)到irq_desc[EINT4t7].handle_irq(irq,desc)函數(shù)執(zhí)行,也就是函數(shù)s3c_irq_demux_extint4t7(irq,desc),該函數(shù)的主要內(nèi)容如下:
static voids3c_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<
static?inline?void?desc_handle_irq(unsigned int irq, struct irq_desc *desc)
函數(shù)s3c_irq_demux_extint4t7()的作用,就是根據(jù)寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK重新計算中斷號,這個時候?qū)⒂嬎愠稣嬲闹袛嗵朓RQ_EINT5,然后通過desc_handle_irq(irq, irq_desc + irq)來調(diào)用irq_desc[EINT5].handle_irq(irq,desc)。此后的過程與EINT0發(fā)生中斷后的執(zhí)行過程類似。
可見,由于S3C2440在設(shè)計的時候最多允許32個中斷源(剩余的中斷源采用子中斷的方式),所以匯編階段根據(jù)INTOFFSET確定的中斷號取值范圍為IRQ_EINT0~IRQ_EINT0+31。也就是說asm_do_IRQ函數(shù)的參數(shù)irq的取值范圍只有32個值,發(fā)生中斷可能是一個實際的中斷號,也可能是一組中斷的中斷號。如果是一個實際的中斷號,那么直接跳轉(zhuǎn)到該中斷號對應(yīng)irq_desc結(jié)構(gòu)體下的handle_irq()執(zhí)行即可;如果是一組中斷的中斷號,那么將通過這組中斷對應(yīng)的irq_desc結(jié)構(gòu)體下的handle_irq()先來確定實際發(fā)生中斷的中斷號,然后再來執(zhí)行其中斷入口處理函數(shù)。
{
? ? desc->handle_irq(irq, desc);
}
?
評論
查看更多