S5PV210 的中断体系

1、S5PV210的向量中断控制器

       可以将异常处理分为两个阶段来理解。第一个阶段是异常向量表跳转;第二个阶段就是进入了真正的异常处理程序irq_handler后的部分。

2、发生中断时CPU的响应过程

       当产生一个中断时,首先PC就会跳转到异常向量表对应的异常入口地址处执行中断程序(这一步做了中断现场保护),然后跳转到真正的中断处理程序执行对应的中断服务函数。最后恢复现场,中断处理完成。

3、中断处理的第一阶段处理

      (1)第一个阶段之所以能够进行,主要依赖于CPU设计时提供的异常向量表机制。第一个阶段的主要任务是从异常发生到响应异常并保存和恢复现场、跳转到真正的异常处理程序处。

      (2)第二个阶段的目的是识别多个中断源中究竟是哪一个发生了中断,然后调用相应的中断处理程序来处理这个中断。

4、中断处理的第二阶段处理

      (1)第一个问题,怎么找到具体是哪一个中断:S5PV210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。210没有子中断寄存器,每个中断源都是并列的。当中断发生时,在isr_handler中依次去查询4个中断源寄存器,看哪一个的哪一位被置1,则这个为对应的寄存器就发生了中断,即找到了中断编号。

      (2)第二个问题,怎么找到对应的isr的问题:210中支持的中断源很多,如果还使用2440的那一套来寻找isr地址就太慢了,太影响实时性。于是210开拓了一种全新的寻找isr的机制。210中提供了很多寄存器来解决每个中断源对应isr的寻找问题,实现的效果是当发生相对应中断时,硬件会自动的将相应isr地址推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。

5、S5PV210中断处理的主要寄存器

     5.1、VICnINTENABLE和VICnINTENCLEAR

        (1)VICnINTENABLE对应interrupt enable,INTENCLEAR对应interrupt enable clear

        (2)INTENABLE寄存器负责相应的中断的使能,INTENCLEAR寄存器负责相应的中断的禁止。

        (3)当我们想使能某个中断时,只要在这个中断编号对应的VICnINTENABLE的相应位置1即可;如果想禁止某个中断时源       时,只要向VICnINTENCLEAR中的相应位写1即可。

        注意:这里的设计一共有两种,有些CPU的的中断使能和禁止是一个寄存器位,写1就使能写0就禁止(或者相反),这样的中断使能设计就要非常小心,要用读改写三部曲来完成;另一种就是使能和禁止寄存器分开为两个寄存器,要是能就写使能寄存器,要禁止就写禁止寄存器。这样的好处是我们在使能或者禁止操作时不用读改写,直接写即可。

     5.2、VICnINTSELECT

        (1)设置各个中断的模式为irq还是fiq。一般都设置成irq。

        (2)irq和fiq究竟有什么区别。210中支持两种中断,irq和fiq。irq是普通中断,fiq是快速中断。快速中断提供一种更快的响应处理的中断通道,用于对实时性要求很高的中断源。fiq在CPU设计时预先提供一些机制保证fiq可以被快速处理,从而保证实时性。fiq的限制就是只能有一个中断源被设置为fiq,其他都是irq。

        (3)CPU如何保证fiq比irq快?有两个原因:第一,fiq模式有专用的r8~r12,因此在fiq的isr可以直接使用r8~r12而不用保存,这就能节省时间。第二,异常向量表中fiq是最后一个异常向量入口,因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次。

     5.3、VICnIRQSTATUS和VICnFIQSTATUS

        (1)中断状态寄存器,是只读的。当发生了中断时,硬件会自动将该寄存器的对应位置1,表示中断发生了。软件在处理中断第二阶段的第一阶段,就是靠查询这个寄存器来得到中断编号的。

     5.4、VICnVECTPRIORITY0~VICnVECTPRIORITY31

        (1)中断优先级设置寄存器,设置多少个中断同时发生时先处理谁后处理谁的问题。一般来说高优先级的中断可以打断低优先级的中断,从而嵌套处理中断。也有些硬件/软件可以设置不支持中断嵌套。

     5.5、VICnVECTADDR0~VICnVECTADDR31、VICnADDR

        (1)这些寄存器和210中断处理第二阶段的第二阶段有关。

        (2)VICnVECTADDR0~VICnVECTADDR31这32个寄存器分别存放真正的各个中断对应的isr的函数地址。相当于每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可。

        (3)VICnADDR这个寄存器是只需要读的,它里面的内容是由硬件自动设置的。当发生了相应中断时,硬件会自动识别中断编号,并会自动找到这个中断的VECTADDR寄存器,然后将其读出复制到VICnADDR中,供我们使用。这样的设计避免了软件查找中断源和isr,节省了时间,提高了210的中断响应速度。

6、S5PV210中断处理的编程实践

    6.1、中断控制器初始化

        主要工作:第一阶段绑定异常向量表到异常处理程序;禁止所有中断源;选择所有中断类型为IRQ;清空VICnADDR寄存器。

/*
 *函数功能:绑定第一阶段异常向量表并初始化中断控制器
 *被调函数:main()
 */
void system_init_exception(void)
{
	//第一阶段处理,绑定异常向量表(个异常入口函数地址)
	r_exception_reset 	 = (unsigned int)reset_exception;
	r_exception_undef	 = (unsigned int)undef_exception;
	r_exception_sotf_int = (unsigned int)sotf_int_exception;
	r_exception_prefetch = (unsigned int)prefetch_exception;
	r_exception_data 	 = (unsigned int)data_exception;
	r_exception_irq 	 = (unsigned int)IRQ_handle;
	r_exception_fiq 	 = (unsigned int)IRQ_handle;

	//初始化中断控制器的基本寄存器
	intc_init();
}

/*
 *函数功能:清除需要处理的中断的中断处理函数的地址
 *被调函数:intc_init()
 */
void intc_clearvectaddr(void)
{
	//VICnADDR:当前正在处理的中断的中断处理函数的地址
	VIC0ADDR = 0;
	VIC1ADDR = 0;
	VIC2ADDR = 0;
	VIC3ADDR = 0;
}

/*
 *函数功能:初始化中断控制器(禁止所有中断;选中断类型;清VICnADDR)
 *被调函数:system_init_exception()
 */
void intc_init(void)
{
	//禁止所有中断
	//因为中断一旦打开,由于外部或者硬件自己的原因产生中断后一定会寻找isr
	//而我们可能认为自己用不到这个中断就没有提供相应的isr,这时他自动拿到的就是乱码
	//则程序就很容易跑飞,所以不用的中断一定要关掉
	//一般的做法是先把所有的中断都关掉,在逐一打开自己感兴趣的中断
	//一旦打开,就必须给这个中断提供相应的isr并绑定
	
	VIC0INTENCLEAR = 0xffffffff;
	VIC1INTENCLEAR = 0xffffffff;
	VIC2INTENCLEAR = 0xffffffff;
	VIC3INTENCLEAR = 0xffffffff;

	//选择中断类型为IRQ
	VIC0INTSELECT = 0x0;
	VIC1INTSELECT = 0x0;
	VIC2INTSELECT = 0x0;
	VIC3INTSELECT = 0x0;

	//清除VICnADDR
	intc_clearvectaddr();
}

    6.2、中断的使能与禁止

        先根据中断号判断这个中断属于哪个VIC,然后再用中断源减去这个VIC的偏移量,得到这个中断号在本VIC中的偏移量,然后1<

/*
 *函数功能:使能中断
 */

//VICnINTENABLE 寄存器的相应中断源对应位为 1 时使能相应中断
//通过传参的intnum来使能某个具体的中断源,中断号在interrupt.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
{
	unsigned long temp;

	//确定intnum在那个寄存器的哪一位
	//intnun < 32就是0~31,必然在VIC0
	if(intnum < 32)
	{
		temp = VIC0INTENABLE;   //读出寄存器原来的值
		temp |= (1 << intnum);  //中断源相应位置 1
		VIC0INTENABLE = temp;   //使能相应中断
	}

	//VIC1
	else if(intnum < 64)
	{
		temp = VIC1INTENABLE;
		temp |= (1 << (intnum - 32));
		VIC1INTENABLE = temp;
	}

	//VIC2
	else if(intnum < 96)
	{
		temp = VIC2INTENABLE;
		temp |= (1 << (intnum - 64));
		VIC2INTENABLE = temp;
	}

	//VIC3
	else if(intnum < NUM_ALL)
	{
		temp = VIC3INTENABLE;
		temp |= (1 << (intnum - 96));
		VIC3INTENABLE = temp;
	}

	//NUM_ALL:使能所有中断
	else
	{
		VIC0INTENABLE = 0xFFFFFFFF;
        VIC1INTENABLE = 0xFFFFFFFF;
        VIC2INTENABLE = 0xFFFFFFFF;
        VIC3INTENABLE = 0xFFFFFFFF;
	}
}

    6.3、绑定自己实现的isr到VICnVECTADDR

        (1)搞清楚两个寄存器的区别:VICnVECTADDR和VICnADDR

        (2)VICnVECTADDR寄存器一共有4 x 32个,每个中断源都有一个VECTADDR寄存器,我们应该将自己为这个中断源写的isr地址丢到这个中断源对应的VECTADDR寄存器中即可。

/*
 *函数功能:把isr入口地址存到VICnVECTADDR寄存器中
 *被调函数:main()
 */

//绑定我们写的isr到VICnVECTADDR寄存器
//绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
//等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可
//参数:intnum 是 interrupt.h 定义的物理中断号,handler是函数指针,就是我们写的isr
//VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个数组
//(这个数组就是函数指针数组)的首地址,然后具体计算每一个中断的时候只需要首地址 + 偏移量即可

void intc_setvectaddr(unsigned long intnum, void (* handler)(void))
{
	//VIC0
	if(intnum < 32)
	{
		*((volatile unsigned long *)(VIC0VECTADDR + 4*(intnum - 0))) = (unsigned)handler;
	}

	//VIC1
	else if(intnum < 64)
	{
		*((volatile unsigned long *)(VIC1VECTADDR + 4*(intnum - 32))) = (unsigned)handler;
	}

	//VIC2
	else if(intnum < 96)
	{
		*((volatile unsigned long *)(VIC2VECTADDR + 4*(intnum - 64))) = (unsigned)handler;
	}

	//VIC3
	else
	{
		*((volatile unsigned long *)(VIC3VECTADDR + 4*(intnum - 96))) = (unsigned)handler;
	}
}

    6.4、真正的中断处理程序如何获取isr

        (1)当发生中断时,硬件会自动把相应中断源的isr地址从VICnVECTADDR寄存器中推入VICnADDR寄存器中,所以我们第二阶段isr_handler中,只需要到相应的VICnADDR中去拿出isr地址,调用执行即可。

    总结:绑定isr地址到VICnVECTADDR寄存器和中断发生时第二阶段的第二阶段如何获取isr地址,这两步是相关的。这两个的技术结合,就是我们一直在说的210的硬件自动寻找isr的机制。

/*
 *函数功能:中断处理程序
 */

//真正的中断处理程序,这里不考虑保护/恢复现场
void irq_handler(void)
{
	//我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断的isr
	//虽然硬件已经把isr放入了VICnADDR中,但是因为有4个,所以必须先去用软件检查出来到底是哪个VIC
	//中断了,也就是说isr到底在哪个VICnADDR寄存器中
	
	unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};

	int i;

	void (* isr)(void) = NULL;

	for(i = 0;i < 4;i++)
	{
		//发送一个中断时,4个VIC中有3个全是0,1个的其中一位不是0
		if(intc_getvicirqstatus(i) != 0)
		{
			isr = (void (*)(void)) vicaddr[i];  //将产生中断的那一个寄存器里的isr程序的地址拿出来
			break;
		}
	}

	(* isr)();     //通过函数指针来调用函数执行我们写的isr程序
}

7、整个中断的流程梳理

        第一部分:为中断响应而做的准备工作
            1、初始化中断控制器
            2、绑定写好的isr到中断控制器
            3、相应中断的所有条件使能

        第二部分:硬件产生中断时如何自动执行isr
            1、经过异常向量表跳转到IRQ/FIQ的入口

IRQ_handle:
	//设置IRQ模式下的栈
	ldr sp, =IRQ_STACK

	//保存LR
	//因为有ARM流水线,所以PC的值会比真正执行的代码+8
	sub lr, lr, #4

	//保存r0~r12和lr到IRQ模式下的栈
	stmfd sp!, {r0-r12, lr}

	//在此调用真正的isr来处理中断
	bl irq_handler

	//处理完成后恢复现场,其实就是做中断返回,关键是将r0~r12, pc, cpsr一起恢复
	ldmfd sp!, {r0-r12, pc}^


            2、做中断现场保护,然后跳入isr_handler
            3、在isr_handler中先去搞清楚那个VIC中断了,再直接去这个VIC的ADDR寄存器中取isr来执行即可。
            4、isr执行完,中断现场恢复,直接返回继续做常规任务。

 

参考来源:朱有鹏物联网课堂笔记!
 

你可能感兴趣的:(ARM)