硬件中断信号处理

关于中断,可以简单的划分为两类,一类是软中断(比如:系统调用,系统报错等);另一类则是硬中断(即硬件设备所产生的中断)。这里,来分析一下内核是如何识别并处理这些中断的。

首先,了解一下MIPS架构中是如何通过寄存器来识别并处理中断的。在MIPS架构的CPU中,其内部存在的协处理器中有许多的功能不同的寄存器,其中与中断相关的寄存器有以下几个:

寄存器名称作用
Status处理器状态与控制寄存器,在该寄存器中与中断向量入口地址相关的为BEV位域
IntCtl中断系统状态与控制寄存器,该寄存器中的VS位域代表了各个中断向量入口地址的间距
Cause存放上次异常原因(MIPS将中断也归类为异常的一种),其中IV位域表示中断异常向量入口控制位(1:使用特殊中断向量0x200;0:使用通用异常向量0x180),ExcCode位域表示异常类型(0x00:中断)
EBase异常入口基址寄存器,其中Exception Base位域左移12位为异常入口向量基址

当发生中断时,内核会根据EBase寄存器以及偏移量来跳转到异常处理入口地址处(因为中断也属于异常的一种形式),而关于EBase和偏移量的大小则是根据Status和Cause寄存器来确定的,如下:

异常向量基址

例外Status.BEV=0Status.BEV=1
基地址EBase 63…12 or 0x0000xFFFF.FFFF.BFC0.0200

异常向量偏移

例外向量偏移
中断(Cause.IV=0)0x180
中断(IntCtl.VS=0 且 Cause.IV=1)0x200
中断(Status.BEV=1 且 Cause.IV=1)0x200
中断(Cause.IV=1 且 Status.BEV=0 且 IntCtl.VS!=0)0x200 + (中断向量号 × (IntCtl.VS or 0xb00000))

这里的“or”代表或操作。所以当处理器接收到中断信号后,会根据上述的寄存器跳转到所对应的异常处理入口。

而在内核中MIPS架构提供的异常处理入口为except_vec3_generic函数,随后该函数通过读取Cause寄存器中的ExCode来判断是哪种异常类型,并通过全局变量exception_handlers数组来找到对应的入口函数,如果是中断类型,则会去调用handle_int()函数。关于上述这些,设置异常处理入口地址,以及根据类型找到对应的处理函数,都是在内核的初始化阶段trap_init()函数中来完成的。

void __init trap_init(void)
{
	...
	set_handler(0x180, &except_vec3_generic, 0x80);
	//关于该函数,其内部实现如下:
	//void set_handler(unsigned long offset, void *addr, unsigned long size)
	//{
	//	...
	//	memcpy((void *)(ebase + offset), addr, size);
	//	...
	//}
	//结合上边对寄存器的描述,可以看出内核将except_vec3_generic的入口地址复制到了ebase+offset处,即异常处理入口地址。
	...
	set_except_vector(EXCCODE_INT, using_rollback_handler() ? rollback_handle_int : handle_int);
	//该函数的内部实现如下:
	//void __init *set_except_vector(int n, void *addr)
	//{
	//	...
	//	old_handler = xchg(&exception_handlers[n], handler);
	//	...
	//}
	//该函数主要将中断处理函数rollback_handle_int()的地址存放到全局变量exception_handlers数组中,即异常向量中。注意,这里不一定是前者,只是自己分析代码时进行的假设。如果是后者,则会直接去执行handle_int()。
}

根据上述代码,可知内核在trap_init中对异常的处理入口,以及异常类型的处理入口进行了设置。关于except_vec3_generic以及rollback_handle_int的实现,均在arch/mips/kernel/genex.S文件中。

NESTED(except_vec3_generic, 0, sp)
	.set push
	.set noat
	mfc0 k1, CP0_CAUSE
	andi k1, k1, 0x7c
#ifdef CONFIG_64BIT
	dsll k1, k1, 1
#endif
	PTR_L k0, exception_handlers(k1)
	jr k0
	.set pop
	END(except_vec3_generic)

可以看到,当接收到中断信号后,首先由except_vec3_generic()函数来进行处理,读取中断号,并调用exception_handlers数组中对应下标中保存的中断处理函数,即rollback_handle_init()。

这里,存放的为rollback_handle_init函数,该函数是通过宏来定义的,如下:

.macro  BUILD_ROLLBACK_PROLOGUE handler
        FEXPORT(rollback_\handler)
        .set    push
        .set    noat
        MFC0    k0, CP0_EPC
        PTR_LA  k1, __r4k_wait
        ori     k0, 0x1f        /* 32 byte rollback region */
        xori    k0, 0x1f
        bne     k0, k1, \handler
        MTC0    k0, CP0_EPC
        .set pop
        .endm

        .align  5
BUILD_ROLLBACK_PROLOGUE handle_int

在当前的宏定义中可以看出,这里调用了__r4k_wait函数做了处理,随后在调用handle_init()函数。

关于handle_init()函数,其主要完成中断处理前期的任务,比如:关闭中断,保护现场等。随后调用plat_irq_dispatch函数来路由中断。plat_irq_dispatch()通过读取Cause、Status、SR(IM)来得知当前中断所在的引脚(IP0~IP7)。

BUILD_ROLLBACK_PROLOGUE handle_int
NESTED(handle_int, PT_SIZE, sp)
	...
2:
	jal	plat_irq_dispatch
	...
	j	ret_from_irq
	...
	END(handle_int)

从这之后,便由plat_irq_dispatch()进行中断路由分发,来选择对应的真正的中断处理函数来进行处理。所以,结合上述的分析,mips架构响应中断处理的顺序如下:

except_vec3_generic()
	  |
	  rollback_handle_int()
	  		  |
	  		  __r4k_wait()
	  		  	|
	  		  	handle_int()
	  		  		  |
	  		  		  plat_irq_dispatch()

注意,异常入口不一定是except_vec3_generic。因为mips架构中存在其他的异常入口函数,比如:exept_vec3_r4000。

当开始对中断进行路由处理时,内核在这之前需要对内核中的数据结构进行初始化。在内核启动阶段的代码中可以看到init_IRQ()函数,顾名思义,该函数主要的任务便是初始化中断数据结构。其实现如下:

void __init init_IRQ(void)
{
	int i;
	unsigned int order = get_order(IRQ_STACK_SIZE);
	//IRQ_STACK_SIZE被定义为2^14,该函数的作用便是获取到指数14。
	for (i = 0; i < NR_IRQS; i++)
		irq_set_noprobe(i);
	//NR_IRQS被定义为128,另外,在init_IRQ函数之前,执行early_irq_init函数对中断结构体struct irq_desc进行组织与初始化。所以,irq_set_noprobe函数通过中断号来查找所对应的struct irq_desc结构体对象,并修改其中相关的成员参数。
	if (cpu_has_veic)
		clear_c0_status(ST0_IM);
	arch_init_irq();
	//该函数主要对所使用的中断控制器进行初始化。而中断初始化的过程主要通过struct irq_desc、struct irq_action、struct irq_chip、struct irq_domain这几个结构体来完成。
	for_each_possible_cpu(i) {
		void *s = (void *)__get_free_pages(GFP_KERNEL, order);
		irq_stack[i] = s;
		pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n", i, irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE);
	}
	//该循环主要为每一个中断所对应的栈来分配空间,每个栈的空间大小为2^14. 
}

当中断初始化完成之后,便可以利用中断数据结构来处理中断。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值