本文以s3c2410为例详述linux中断处理机制,力求说明linux内核通用中断流程,以及与特定芯片相关实际外设中断初始化方式,并详细分析从外设产生中断到用户定义中断处理函数被调用的完整流程。
相关文件
include/asm-arm/mach/irq.h 定义irqdesc结构体,中断主要描述符。
include/asm-arm/mach/irq.h 定义irqchip结构体,和芯片相关函数集。
include/linux/interrupt.h 定义irqaction结构体,和中断处理相关结构。
arch/arm/kernel/trips.c 定义trip_init()函数,完成中断向量表拷贝。
arch/arm/kernel/irq.c 定义init_IRQ()函数,完成具体芯片相关初始化。
arch/arm/kernel/entry-armv.S 定义中断向量表,汇编级中断处理函数等,包括统一的压栈、设置处理器模式、调用c语言通用中断处理函数等。
arch/arm/kernel/irq.c 定义arm_do_IRQ()函数,通用中断处理入口函数。
arch/arm/kernel/irq.c 定义request_irq()函数,驱动程序注册中断函数调用。
arch/arm/mach-s3c2410/mach-smdk2410.c
文件最末尾定义了体系相关结构,函数指针init_arch_irq在setup_arch()中指向smdk2410_init_irq,在
smdk2410_init_irq()中完成芯片相关初始化。
数据结构
该函数指针定义在include/asm-arm/mach/irq.h文件中,irqdesc结构在芯片相关初始化时会设置其为do_edge_IRQ(),do_level_IRQ(),do_simple_IRQ()等值。
typedef void (*irq_handler_t)(unsigned int, struct irqdesc *, struct pt_regs *);
/*
中断描述结构,整个内核用数组struct irqdesc irq_desc[NR_IRQS]记录所有的描述符,NR_IRQS和体系结构有关,比如ARM中NR_IRQS=32
struct irqdesc {
/*
* 此处中断处理函数并不是实际和业务相关的中断处理函数
* linux中断处理允许共享中断线,因此和业务具体相关的处理函数挂在中断线上,即使该中断没有共享
* 依旧以中断线处理函数处理,struct irqaction *action即为中断线处理函数链表。
*
*/
irq_handler_t handle; /*中断处理函数,指do_edge_IRQ(),do_level_IRQ(),do_simple_IRQ()等*/
struct irqchip *chip; /*中断芯片级处理函数*/
struct irqaction *action; /*中断处理函数链表,共享中断线的中断在该链表中对应一项*/
struct list_head pend;
void *chipdata;
void *data;
unsigned int disable_depth;
unsigned int triggered: 1; /* IRQ has occurred */
unsigned int running : 1; /* IRQ is running */
unsigned int pending : 1; /* IRQ is pending */
unsigned int probing : 1; /* IRQ in use for a probe */
unsigned int probe_ok : 1; /* IRQ can be used for probe */
unsigned int valid : 1; /* IRQ claimable */
unsigned int noautoenable : 1; /* don't automatically enable IRQ */
unsigned int unused :25;
struct proc_dir_entry *procdir; /*对应/proc下的一个目录结构*/
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
/*
* IRQ lock detection
*/
unsigned int lck_cnt;
unsigned int lck_pc;
unsigned int lck_jif;
};
中断芯片描述符,挂接具体芯片处理代码,内核移植时需要设置这些函数。
struct irqchip {
/*
* Acknowledge the IRQ.
* If this is a level-based IRQ, then it is expected to mask the IRQ
* as well.
*/
void (*ack)(unsigned int);
/*
* Mask the IRQ in hardware.
*/
void (*mask)(unsigned int);
/*
* Unmask the IRQ in hardware.
*/
void (*unmask)(unsigned int);
/*
* Ask the hardware to re-trigger the IRQ.
* Note: This method _must_ _not_ call the interrupt handler.
* If you are unable to retrigger the interrupt, do not
* provide a function, or if you do, return non-zero.
*/
int (*retrigger)(unsigned int);
/*
* Set the type of the IRQ.
*/
int (*set_type)(unsigned int, unsigned int);
/*
* Set wakeup-enable on the selected IRQ
*/
int (*set_wake)(unsigned int, unsigned int);
#ifdef CONFIG_SMP
/*
* Route an interrupt to a CPU
*/
void (*set_cpu)(struct irqdesc *desc, unsigned int irq, unsigned int cpu);
#endif
};
中断响应结构,多个中断共享一个中断号时,该结构会形成一个链表,在中断到来时会遍历整个链表执行中断函数handler。
struct irqaction {
/*具体业务处理函数,函数指针通过系统调用request_irq()设定*/
irqreturn_t (*handler)(int, void *, struct pt_regs *);
unsigned long flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
};
中断初始化
下面以s3c6410中断初始化为例,说明linux中断初始化过程:
中断初始化主要在两个函数trip_init()、init_IRQ()中进行,trip_init()主要初始化异常相关中断,init_IRQ()主要对外设中断进行初始化设置。
trip_init()函数定义在arch/arm/kernel/traps.c中,其作用是:将arch/arm/kernel/entry-
armv.S中定义的异常向量表拷贝到0x0000 0000或0xFFFF 0000(设置为高端地址模式)地址处。ARM处理器在中断到来时,
根据异常类型,自动跳转到0x0000 0000或0xFFFF 0000(设置为高端地址模式)开始的位置,执行异常处理函数。
void __init trap_init(void)
{
/*
* __stubs_start ~ __stubs_end中存放的是中断处理函数
* 中断函数vector_irq等以宏的形式定义,必要操作是保存堆栈,设置cpu模式等, 再经过层层跳转,最后调用asm_do_IRQ()函数处理中断
*/
extern char __stubs_start[], __stubs_end[];
/*
* entry-armv.S定义的异常向量表:
* .globl __vectors_start
* __vectors_start:
* swi SYS_ERROR0
* b vector_und + stubs_offset
* ldr pc, .LCvswi + stubs_offset
* b vector_pabt + stubs_offset
* b vector_dabt + stubs_offset
* b vector_addrexcptn + stubs_offset
* b vector_irq + stubs_offset
* b vector_fiq + stubs_offset
*
* .globl __vectors_end
* __vectors_end:
* 其中:stubs_offset = __vectors_start + 0x200 - __stubs_start
*/
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
/*
* 下面的代码即将异常向量表拷贝到特定位置,cpu在中断到来时会自动跳转到相应位置
*
*/
memcpy((void *)0xffff0000, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)0xffff0200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)0xffff1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(0xffff0000, 0xffff0000 + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);/* 修改页的权限,保证只有内核才能访问异常向量表等*/
}
init_IRQ()函数将struct irqdesc irq_desc[NR_IRQS]中链表进行串接起来,并调用体系相关
初始化函数init_arch_irq();init_arch_irq()是函数指针,在setup_arch()中以init_arch_irq =
mdesc->init_irq 进行赋值。mdesc是machine_desc类型,针对不同芯片有不同值,根据启动项可以确定
* mdesc的值,以SMDK2410芯片为例,对应结构为:
* MACHINE_START(SMDK2410, "SMDK2410")
* .phys_io = S3C2410_PA_UART,
* .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
* .boot_params = S3C2410_SDRAM_PA + 0x100,
* .map_io = smdk2410_map_io,
* .init_irq = smdk2410_init_irq,
* .timer = &s3c24xx_timer,
* MACHINE_END
所以,实际即调用smdk2410_init_irq()进行芯片级中断初始化
void __init init_IRQ(void)
{
struct irqdesc *desc;
int irq;
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
for (irq = 0, desc = irq_desc; irq < NR_IRQS; irq++, desc++) {
*desc = bad_irq_desc;
INIT_LIST_HEAD(&desc->pend);
}
init_arch_irq();
}
从上面分析可知,芯片级中断初始化实际调用了芯片对应中断初始化函数。以SMDK2410芯片初始化函数为例,调用流程为init_arch_irq()->smdk2410_init_irq()->
s3c24xx_init_irq(),在s3c24xx_init_irq()函数中根据芯片特性,设置相关信息。
s3c24xx_init_irq()函数代码如下:
void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;
irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");
/* first, clear all interrupts pending... */
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C2410_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_INTPND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C2410_SRCPND);
__raw_writel(pend, S3C2410_INTPND);
printk("irq: clearing pending status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);
if (pend == 0 || pend == last)
break;
printk("irq: clearing subpending status %08x\n", (int)pend);
__raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}
/* register the main interrupts */
irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");
for (irqno = IRQ_BATT_FLT; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */
switch (irqno) {
/* deal with the special IRQs (cascaded) */
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
set_irq_chip(irqno, &s3c_irq_level_chip);
set_irq_handler(irqno, do_level_IRQ);
break;
case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;
default:
//irqdbf("registering irq %d (s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_chip);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
}
/* setup the cascade irq handlers */
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
/* external interrupts */
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, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
/* register the uart interrupts */
irqdbf("s3c2410: registering external interrupts\n");
for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart0);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
irqdbf("s3c2410: registered interrupt handlers\n");
}
中断处理流程
以irq中断为例,外设产生中断后,pc指针会指向0x0000 0018或0xffff
0018处(当然,由于cpu流水线作业问题,其值可能有个偏移),在0xffff
0018处是一条跳转指令到具体中断处理函数中(entry-armv.S中详细定义了,前面已经说明过),宏展开后的处理函数如下:
vector_irq:
.if 4
sub lr, lr, #4
.endif
/*保存寄存器信息到堆栈中*/
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
/*设置cpu为SVC模式*/
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
/*跳转表必须紧接着vector_irq存放,在下面代码中会进入跳转表
* lr 是中断刚开始时的SPSR,即被中断代码的CPSR,其低4 位表示中断之前的模式
* 所以,下面代码的功能是根据进入中断时的模式,用户模式或内核模式,决定是进入
* __irq_usr或__irq_svc
*/
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
跳转表如下:
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
以__irq_usr为例,其定义如下:
__irq_usr:
usr_entry
get_thread_info tsk
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler /*进入具体中断处理,其它为流程代码*/
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
strne r0, [r0, -r0]
#endif
mov why, #0
b ret_to_user
irq_handler定义如下:
get_irqnr_and_base定义在include\asm-arm\arch-xxx\entry-macro.S中,作用是读取中断号和中断信息。中断号是通过查询VIC相应寄存器获得的,详细内容可以查看以前写的一篇关于向量中断控制器VIC的文章。
.macro irq_handler
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
/*中断函数参数为 r0 = irq number, r1 = struct pt_regs * */
adrne lr, 1b
bne asm_do_IRQ
接下来调用c语言中断处理函数asm_do_IRQ(),该函数定义在arch/arm/kernel/irq.c中,进入该函数后,按照统一流程调用irq_desc结构中设置的函数进行中断处理。
/*
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irqdesc *desc = irq_desc + irq;
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
spin_lock(&irq_controller_lock);
desc_handle_irq(irq, desc, regs);
/*
* Now re-run any pending interrupts.
*/
if (!list_empty(&irq_pending))
do_pending_irqs(regs);
irq_finish(irq);
spin_unlock(&irq_controller_lock);
irq_exit();
}
这个函数很容易理解,根据中断号从irqdesc[]数组中获得irqdesc结构,再调用desc_handle_irq(irq, desc,
regs),该函数调用desc->handle(irq, desc,
regs),还记得之前介绍irq_handle_t函数指针时说明的内容吗?handle指针指向
do_edge_IRQ(),do_level_IRQ(),do_simple_IRQ()中的某一个,对应边沿触发、电平触发等。以
do_edge_IRQ()为例,先调用芯片相关中断确认函数desc->chip->ack(irq),在该函数中清除相应芯片中断信号、
禁止其它中断等;然后调用__do_irq(irq,
desc->action, regs)
/*
* Level-based IRQ handler. Nice and simple.
*/
void
do_level_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs)
{
struct irqaction *action;
const unsigned int cpu = smp_processor_id();
desc->triggered = 1;
/*
* Acknowledge, clear _AND_ disable the interrupt.
*/
desc->chip->ack(irq);
if (likely(!desc->disable_depth)) {
kstat_cpu(cpu).irqs[irq]++;
smp_set_running(desc);
/*
* Return with this interrupt masked if no action
*/
action = desc->action;
if (action) {
int ret = __do_irq(irq, desc->action, regs);
if (ret != IRQ_HANDLED)
report_bad_irq(irq, regs, desc, ret);
if (likely(!desc->disable_depth &&
!check_irq_lock(desc, irq, regs)))
desc->chip->unmask(irq);
}
smp_clear_running(desc);
}
}
__do_irq(irq, desc->action, regs)函数,该函数主要作用是遍历irqdesc结构中的irqaction结构链表,完成用户定义中断处理函数调用。
static int
__do_irq(unsigned int irq, struct irqaction *action, struct pt_regs *regs)
{
unsigned int status;
int ret, retval = 0;
spin_unlock(&irq_controller_lock);
#ifdef CONFIG_NO_IDLE_HZ
if (!(action->flags & SA_TIMER) && system_timer->dyn_tick != NULL) {
write_seqlock(&xtime_lock);
if (system_timer->dyn_tick->state & DYN_TICK_ENABLED)
system_timer->dyn_tick->handler(irq, 0, regs);
write_sequnlock(&xtime_lock);
}
#endif
if (!(action->flags & SA_INTERRUPT))
local_irq_enable();
status = 0;
do {
ret = action->handler(irq, action->dev_id, regs);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
spin_lock_irq(&irq_controller_lock);
return retval;
}
用户操作接口
驱动程序中使用这些接口函数可以处理中断相关工作,下面列出结构典型的接口,其它请查阅相应文档获得。
linux/interrupt.h
int request_irq(unsigned int irq, irqreturn_t (*handle)(), unsigned long
flags, const char *dev_name, void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
void disable_irq(unsigned int irq);
void enable_irq(unsigned int irq);
浙公网安备 33010602011771号