linux 中断管理(三)

一、中断过程

1、中断的发生

当发生中断时,CPU会跳到异常向量表,处理相应的中断
异常向量表在 arch\arm\kernel\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


         /* 中断入口地址。发生中断时会跳到这里,执行这句跳转语句,
          *+ stubs_offset 是因为异常向量表使用高地址0xffff0000,异常向量copy0xffff0000处 */
	b	vector_irq + stubs_offset   

 
	b	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

如果发生irq中断,就会执行b vector_irq + stubs_offset 这条语,会跳转到下面的代码块
对于vector_stub这个宏,在linux异常处理体系结构有简单的介绍

    /* vector_stub 是汇编下定义的一个宏, 里面是一块代码块,主要用于计算返回地址,计算下一步要跳转的入口地址
    * irq这个宏的名字, 
    * #define IRQ_MODE 0x00000012  
    * 4 这个是用于计算返回地址的
    */
	vector_stub	irq, IRQ_MODE, 4     
    /* 下面要跳转到那个入口,是在 vector_stub 宏里面计算并且跳转的*/
	.long	__irq_usr			@  0  (USR_26 / USR_32)    /* 用户模式下发生irq异常 */
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)    /* 管理模式下发生irq异常 */
	.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

之前在linux异常处理体系结构介绍到,不管是用户模式下发生中断,还是管理模式下发生中断,中断服务函数都是相同的(只是前面需要)。
接下来就以 __irq_usr 分析。

__irq_usr:
    /* usr_entry 也是一个宏,里面的代码块作用是保存 r0、r1 等一些寄存器 */
	usr_entry

#ifdef CONFIG_TRACE_IRQFLAGS      
	bl	trace_hardirqs_off
#endif
	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       /* irq_handler 是一个宏,这个宏也是一个代码块,中断服务函数,也是在这个宏的代码块里面调用 */
#ifdef CONFIG_PREEMPT     
	ldr	r0, [tsk, #TI_PREEMPT]
	str	r8, [tsk, #TI_PREEMPT]
	teq	r0, r7
	strne	r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS    
	bl	trace_hardirqs_on
#endif

	mov	why, #0
	b	ret_to_user

irq_handler 这个宏也是定义在 arch\arm\kernel\entry-armv.S 文件里面

	.macro	irq_handler
	get_irqnr_preamble r5, lr    /*get_irqnr_preamble 是一个宏,里面什么都没做 */
1:	get_irqnr_and_base r0, r6, r5, lr  /* get_irqnr_and_base 也是一个宏,在里面会计算当前的中断号(中断号通过 INTOFFSET 寄存器的某一位来确定)、中断状态等,和struct pt_regs 结构体压栈。*/
	movne	r1, sp    /* 通过堆栈将struct pt_regs* 传给 asm_do_IRQ 函数 */
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, 1b
	bne	asm_do_IRQ   /* 到这里就是跳转过去执行中断服务函数了 */

2、中断的处理

asm_do_IRQ() 函数就是所有中断的服务函数了。 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);
    /* 通过中断号获取 irq_desc 数组项*/
	struct irq_desc *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();
        
    /* 这里处理中断了 */
	desc_handle_irq(irq, desc);

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}

desc_handle_irq 函数里面就是调用用户注册的中断处理函数。 desc_handle_irq函数在 include\asm-arm\mach\Irq.h文件里面定义。代码块如下

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

*补充说明:

asm_do_IRQ函数中参数irq的取值的范围是 IRQ_EINT0 ~ 31, 只有32个取值。 asm_do_IRQ 函数中的 irq 参数可能是一个实际的中断号,也可能是一组中断的中断号,这是由s3c2440的芯片决定的.当发生中断时后 INTPND 寄存器的某一位被置1, INTOFFSET 寄存器的值确定irq参数。每一位实际的中断在irq_desc数组中都有一项与它对应, 他们的数目不止。当asm_do_IRQ函数中irq表示的是"一组"中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出这一组中断的哪一个中断(假设中断号是irqno),然后调用irq_desc[irqno].handle_irq来进一步处理。

以外部中断EINT8~EINT23为例:

(1)中断被触发时,INTOFFSET寄存器的值是5, asm_do_IRQ函数中的参数irq的值为(IRQ_EINT0+5),即 IRQ_EINT8t23。然后将调用irq_desc[IRQ_EINT8t23].handle_irq 函数来处理
(2)irq_desc[IRQ_EINT8t23].handle_irq 在初始化的时候,被初始化为 s3c_irq_demux_extint8
(3)s3c_irq_demux_extint8 函数在 arch\arm\plat-s3c24xx\Irq.c 文件里面定义。它先读取 EINTPEND 和 EINTMASK 寄存器的值来确定发生了那些中断;然后重新计算中断号,然后再调用irq_desc数组项中的handle_irq成员函数。

s3c_irq_demux_extint8 函数的代码如下:

static void
s3c_irq_demux_extint8(unsigned int irq, struct irq_desc *desc)
{    
    /* 读取 EINTPEND 寄存器的值,EINT8 ~ EINT23 发生时相应的位被置1 */
	unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
    /* 读取屏蔽寄存器的值 */
	unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
    /* 清除被屏蔽位 */
	eintpnd &= ~eintmsk;
    /* 清除低8位,EINT8对应位8 ... */
	eintpnd &= ~0xff;	/* ignore lower irqs */

	/* we may as well handle all the pending IRQs here */
    /* 循环处理所有的子中断 */
	while (eintpnd) {
        /* 确定 eintpnd 中为1的最高位  */
		irq = __ffs(eintpnd);
       /* 将该位清零 */
		eintpnd &= ~(1<<irq);
        /* 重新计算中断号:前面计算出,如果 irq = __ffs(eintpnd) 算出的是8,则中断号为8 */
        	irq += (IRQ_EINT4 - 4);
        /* 调用这个中断真正的处理函数(handle_edge_irq函数)  */
		desc_handle_irq(irq, irq_desc + irq);
	}

}

(4)IRQ_EINT8 ~ IRQ_EINT23 这几个真正的中断处理函数入口,在init_IRQ函数初始化中断的时候被初始化 handle_edge_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 */
    /* 调用 chip 底层的操作函数 清除中断 */
	desc->chip->ack(irq);

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

	do {
		/* 依次调用 action 链表里面的处理函数(用户注册的函数都是挂在action链表里面的) */
		action_ret = handle_IRQ_event(irq, action);
		

	} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

}

handle_IRQ_event 依次调用 action 链表里面的处理函数;这个函数在 kernel\irq\Handle.c 文件里面定义,代码块如下

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{

	
    ...
     /* 依次调用 action 链表的成员函数*/
	do {
		ret = action->handler(irq, action->dev_id);
		if (ret == IRQ_HANDLED)
			status |= action->flags;
		retval |= ret;
		action = action->next;
	} while (action);

    ...
	return retval;
}

注意: 上面分析的是边缘触发的中断过程,对于边缘触发调用的是handle_edge_irq()函数处理,对于电平触发的调用的是 handle_level_irq 函数,处理的方式都是类似的,只是电平触发的还要屏蔽这个中断,处理完之后还要开启这个中断。

总结:

(1) 发生中断时,中断向量调用asm_do_IRQ,传入中断号irq
(2) asm_do_IRQ 函数根据中断号irq调用irq_desc[irq].handle_irq。他是这个中断的处理函数入口。对于电平触发这个函数入口通常是handle_level_irq,对于边缘触发入口函数通常是handle_edge_irq
(3) 入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断。
(4) 逐个调用用户注册在 irq_desc[irq].action 链表中注册的中断处理函数
(5) 入口函数是handle_level_irq时,处理完之后还要开启这个中断。

posted @ 2019-12-08 22:53  古澜  阅读(563)  评论(0编辑  收藏  举报