以下内容源于朱有鹏课程的学习,如有侵权,请告知删除。
参考资料:http://www.cnblogs.com/biaohc/p/6354068.html
(1)异常向量表是CPU中某些特定地址的特定定义。当中断发生的时候,中断要想办法通知CPU去处理中断,怎么做到?依靠异常向量表。
(2)在CPU设计时,事先定义了CPU中一些特定地址作为特定异常的入口地址。
(1)对SoC来说,发生复位、软中断、中断、快速中断、取指令异常、数据异常等,我们都统一叫异常。因此,中断其实是异常的一种。
(2)异常的定义就是突发事件,打断了CPU的正常常规业务,CPU不得不跳转到异常向量表中去执行异常处理程序。
(1)编译器把函数体对应的代码段和这个函数的函数名(实质是符号)对应起来
(2)将异常处理程序的首地址和异常向量表绑定后,异常处理初步阶段就完成了。
到目前可以保证相应异常发生后,硬件自动跳转到对应异常向量表入口去执行时,可以执行到我们事先绑定的函数。
(1)中断处理,需要保护现场和恢复现场
(2)保存现场
(3)为什么要保存LR寄存器?
(4)恢复现场主要是恢复:r0-r12,pc,cpsr
(1)怎么找到具体是哪个中断?
(2)怎么找到对应的isr?
(1)怎么找到具体是哪个中断?
(2)怎么找到对应的isr?
(1)VICnINTENABLE寄存器负责相应的中断的使能,VICnINTENCLEAR寄存器负责相应的中断的禁止。
(2)这里的n=0,1,2,3共四个寄存器,每个寄存器都是32bit,每个bit对应一个中断源是否使能 。
(3)例如下面的代码
// 使能中断
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
{
unsigned long temp;
// 确定intnum在哪个寄存器的哪一位
// <32就是0~31,必然在VIC0
if(intnum<32)
{
temp = VIC0INTENABLE;
temp |= (1<
// 禁止中断
// 通过传参的intnum来禁止某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_disable(unsigned long intnum)
{
unsigned long temp;
if(intnum<32)
{
temp = VIC0INTENCLEAR;
temp |= (1<
2、VICnINTSELECT寄存器
(1)设置各个中断的模式为irq还是fiq。一般都设置成irq。
(2)IRQ和FIQ究竟有何区别?
- 210中支持2种中断,irq和fiq。irq是普通中断,fiq是快速中断。
- 快速中断提供一种更快响应处理的中断通道,用于对实时性要求很高的中断源。
- fiq在CPU设计时预先提供了一些机制保证fiq可以被快速处理,从而保证实时性。
- fiq的限制就是只能有一个中断源被设置为fiq,其他都是irq。
(3)CPU如何保证fiq比irq快?
- 第一,fiq模式有专用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用保存,这就能节省时间;
- 第二,异常向量表中fiq是最后一个异常向量入口。因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,省了些时间。
(4)这里的n=0,1,2,3共四个寄存器,可以设置各个中断的模式。
- VIC0INTSELECT,VIC1INTSELECT,VIC2INTSELECT,VIC3INTSELECT。
(5)代码
// 清除需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{
// VICxADDR:当前正在处理的中断的中断处理函数的地址(即isr的入口地址,见3的代码示例)
VIC0ADDR = 0;
VIC1ADDR = 0;
VIC2ADDR = 0;
VIC3ADDR = 0;
}
// 初始化中断控制器
void intc_init(void)
{
// 禁止所有中断
// 为什么在中断初始化之初要禁止所有中断?
// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr
// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码
// 则程序很可能跑飞,所以不用的中断一定要关掉。
// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须
// 给这个中断提供相应的isr并绑定好。
VIC0INTENCLEAR = 0xffffffff;
VIC1INTENCLEAR = 0xffffffff;
VIC2INTENCLEAR = 0xffffffff;
VIC3INTENCLEAR = 0xffffffff;
// 这里把所有的中断源的中断类型设置为IRQ;其实可以具体到设置每一个中断的中断模式
VIC0INTSELECT = 0x0;
VIC1INTSELECT = 0x0;
VIC2INTSELECT = 0x0;
VIC3INTSELECT = 0x0;
// 清VICxADDR
intc_clearvectaddr();
}
3、VICnIRQSTATUS、VICnFIQSTATUS
(1)中断状态寄存器
- 该寄存器是只读的。
- 当发生了中断时,硬件会自动将该寄存器的对应位置为1,表示中断发生了。
- 软件在处理中断第二阶段的第一阶段,就是靠查询这个寄存器来得到中断编号的。
- 有四个:VIC0IRQSTATUS,VIC1IRQSTATUS,VIC2IRQSTATUS,VIC3IRQSTATUS。
(2)代码示例
// 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
if(ucontroller == 0)
return VIC0IRQSTATUS;
else if(ucontroller == 1)
return VIC1IRQSTATUS;
else if(ucontroller == 2)
return VIC2IRQSTATUS;
else if(ucontroller == 3)
return VIC3IRQSTATUS;
else
{}
return 0;
}
// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{
//printf("irq_handler.\n");
// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
// 对应的isr。
// 虽然硬件已经自动帮我们把isr放入了VICnADDRESS中,但是因为有4个,所以我们必须
// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICnADDRESS寄存器中
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
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];
break;
}
}
(*isr)(); // 通过函数指针来调用函数
}
4、VICnVECTPRIORITY0~VICnVECTPRIORITY31寄存器
- 中断优先级设置寄存器,设置多个中断同时发生时先处理谁后处理谁的问题。
- 一般来说高优先级的中断可以打断低优先级的中断,从而嵌套处理中断。
5、VICnVECTADDR0~VICnVECTADDR31寄存器
(1)这些寄存器和210中断处理第二阶段的第二阶段有关。
(2)VICnVECTADDR0~31这32个寄存器。
- 分别用来存放真正的各个中断对应的isr的函数地址。即中断处理函数的存放地址。
- 相当于每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可。
- n=0,1,2,3;
(3)代码
// 绑定我们写的isr到VICnVECTADDR寄存器
// 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
// 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。
// 参数:intnum是int.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;
}
return;
}
6、VICnADDRESS寄存器
- 内容由硬件自动设置。当发生中断时,硬件自动识别中断编号,并且自动找到这个中断的VECTADDR寄存器,把寄存器的内容复制到VICADDRESS中,以供使用。
- 这样的设计避免了软件查找中断源和isr,节省了时间,提高了210的中断响应速度。
// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{
//printf("irq_handler.\n");
// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
// 对应的isr。(函数指针)
// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须
// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
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];
break;
}
}
(*isr)(); // 通过函数指针来调用函数
}
五、总结
整个中断过程可以分为两部分
第一部分是我们为中断响应而做的预备工作:
- 初始化中断控制器
- 绑定写好的isr到中断控制器
- 相应中断的所有条件使能
第二部分是当硬件产生中断后如何自动执行isr:
- 第一步,经过异常向量表跳转入IRQ/FIQ的入口(IRQ_handl:)
- 第二步,做中断现场保护(在start.S中),然后跳入isr_handler
- 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR寄存器中取isr来执行即可
- 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。
你可能感兴趣的:(ARM裸机)