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< 6.3、绑定自己实现的isr到VICnVECTADDR (1)搞清楚两个寄存器的区别:VICnVECTADDR和VICnADDR (2)VICnVECTADDR寄存器一共有4 x 32个,每个中断源都有一个VECTADDR寄存器,我们应该将自己为这个中断源写的isr地址丢到这个中断源对应的VECTADDR寄存器中即可。 6.4、真正的中断处理程序如何获取isr (1)当发生中断时,硬件会自动把相应中断源的isr地址从VICnVECTADDR寄存器中推入VICnADDR寄存器中,所以我们第二阶段isr_handler中,只需要到相应的VICnADDR中去拿出isr地址,调用执行即可。 总结:绑定isr地址到VICnVECTADDR寄存器和中断发生时第二阶段的第二阶段如何获取isr地址,这两步是相关的。这两个的技术结合,就是我们一直在说的210的硬件自动寻找isr的机制。 7、整个中断的流程梳理 第一部分:为中断响应而做的准备工作 第二部分:硬件产生中断时如何自动执行isr 参考来源:朱有鹏物联网课堂笔记!/*
*函数功能:使能中断
*/
//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;
}
}
/*
*函数功能:把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;
}
}
/*
*函数功能:中断处理程序
*/
//真正的中断处理程序,这里不考虑保护/恢复现场
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程序
}
1、初始化中断控制器
2、绑定写好的isr到中断控制器
3、相应中断的所有条件使能
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执行完,中断现场恢复,直接返回继续做常规任务。