中断
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行
1 cpu处理处理事情的方式
- 轮询:不断查询是否有事情需要处理,如果有则处理。由于需要不断的轮询,效率较低
- 中断:当需要CPU处理的时候,产生一个信号,打断CPU正在做的事情,让CPU处理另一件事情,等另一件事情处理完以后,回到打断之前的地方接着执行。
中断处理需要注意的地方:
- 打断了其他程序的执行,所以中断处理的时候需要尽可能的快,不能在中断处理过程中做耗时很长的事情。
- 打断了其他程序的执行,所以在中断处理的时候,需要先保存现场(CPU的状态和CPU内部寄存器的值:压栈保存),在中断处理结束的时候,需要恢复现场。
2 arm中断处理过程
1、中断也是一种异常,arm核异常处理过程:
当异常产生的时候,ARM核自动做的事情:
- 将CPSR的值拷贝到异常模式的SPSR寄存器
- 设置CPSR相应位
- 进入ARM状态
- 进入对应的异常模式
- 禁止中断
- 保存pc的值到异常模式的lr
- 将pc设置异常向量表的相应位置
程序员需要做的事情:
-
编写异常向量表 (在异常向量表中写跳转指令,跳转指定异常处理函数)
-
设置异常向量表的基地址:告诉ARM核异常向量表的基地址
[1]cortex-A系列之前,异常向量表可以存放在0x0000,0000(低地址) 或0xffff,0000(高地址) cp15(协处理器).c1(寄存器) 决定异常向量存放在高地址还是低地址
[2]cortex-A系列之后,异常向量表可以存放在任意位置cp15(协处理器).c12(寄存器)
保存异常向量表的基地址 -
编写异常处理函数
[1]设置sp寄存器
[2]将通用的寄存器(r0-12)进行压栈保护
[3]调用异常处理函数,处理异常
[4]异常返回 A.恢复r0-r12 (出栈) B.恢复cpsr (spsr->cpsr) C.恢复pc (lr ->pc)
2、异常处理函数中的中断处理过程:
- 获取中断号
- 根据中断号,从中断向量表中找到对应的处理函数
- 执行处理函数
异常处理函数中,获取到中断号后,从中断向量表中查找中断处理函数。
3 中断相关概念
SOC内部的中断源的编号,就是中断号。
在芯片设计时,就确定了可以产生中断的中断源设备。
一个中断号对应的一个中断处理函数。
中断控制器:控制中断的优先级、中断是否允许被处理。
- A、B、C三个中断源都想产生中断,而中断控制器来决定谁来产生中断。
内部中断和外部中断:
- SOC内部控制器产生的中断,是内部中断
- SOC芯片外部管脚通过电平触发的中断,是外部中断:高电平触发、低电平触发、上升沿触发、下降沿触发、双边沿触发
4 arm GIC中断控制器
4.1 GIC支持的中断类型
支持的最常见的三种中断:
-
Peripheral interrupt:外设中断
- Private Peripheral Interrupt (PPI) (专用外设中断):只能对特定的单个处理器产生的中断
- Shared Peripheral Interrupt (SPI):通用公共中断,可以将中断路由到任何指定的处理器组合
-
Software-generated interrupt:SGI,软中断:通过软件向GIC中的GICD_SGIR寄存器写入数据产生的中断。主要是系统内核使用SGIs进行处理器间通信
还支持:Virtual interrupt (虚拟中断)、Maintenance interrupt(维护中断)
4.2 GIC支持的中断源个数
GIC分配中断ID号ID0-ID1019,共1020个中断源。
- 中断号ID32-ID1019用于SPI
- 中断号ID0-ID31用于CPU接口专用的中断。这些中断存储在中断分发器中:ID0-ID15用于SGI,ID16-ID31用于PPI
4.3 Distributor(中断分发器)和CPU interface block(CPU接口单元)
GIC可分为四个部分构成:
中断源 --> 中断分发器(保安) --> CPU接口单元(秘书) --> 处理器核(老板)
所有的中断源,都要先经过中断分发器。
-
中断分发器确定中断优先级,并根据优先级转发中断到CPU接口单元
-
每个CPU接口块都为连接到GIC的处理器提供接口
4.4 中断分组
- 每个中断可以配置为group0或group1。
- group0可以是IRQ或FRQ
- group1只能是IRQ
- group0的中断都是安全中断
- group1中断都是非安全的中断
4.5 中断优先级
GIC中,可以配置8位无符号二进制的中断优先级
- 较低的数字具有较高的优先级,即分配的优先级值越低,中断的优先级越高
5 通过IMX6ULL按键测试中断处理流程
-(1)gpio控制器的初始化(外部中断需要)
void key_irp_gpio_init(void)
{
// Initialize GPIO pins for IR receiver
/* clock enable */
CCM_CCGR1 |= (0x3 << 26);
/* GPIO mode */
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO26 &= ~(0xf << 0);
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO26 |= (0x5 << 0);
/* IRQ register:配置为低电平中断 */
GPIO1->ICR2 &= ~(0x3 << 20);
/* GPIO enable IRQ*/
GPIO1->IMR |= (0x1 << 26);
/* clear status */
GPIO1->ISR |= (0x1 << 26);
}
- (2)GIC中断控制器的初始化,并注册中断处理函数
typedef unsigned int uint32_t;
typedef int (*handler_t)(int id);
//用来记录,每个中断源对应的中断处理函数入口地址
handler_t function_table[NUMBER_OF_INT_VECTORS];
//只能向CPU0注册中断
void request_irq(int irq_num,handler_t handler)
{
int index, nbit;
//使能GIC中断分发器转发给CPU接口单元
GIC->D_CTLR |= (1 << 0);
//使能每个中断ID,让中断分发器转发到CPU接口
index = irq_num >> 5;//index = irq_num / 32
nbit = irq_num - 32 * index;//nbit = irq_num % 32
GIC->D_ISENABLER[index] |= (1 << nbit);
//中断分发器收到中断后,转发给CPU0
GIC->D_ITARGETSR[irq_num] |= (1 << 0);
//允许任意优先级的中断到达CPU,优先级的值越大,级别越低
GIC->C_PMR = (0x1f << 3);
//使能 CPU interface转发给CPU核
GIC->C_CTLR |= (1 << 0);
//记录中断处理函数
function_table[irq_num] = handler;
return;
}
/* 供start/start.S跳转到此中断处理函数 */
void do_irq(void)
{
int irq_num;
handler_t handler;
/* 获取中断号 */
irq_num = (GIC->C_IAR & 0x1FF);
//通知中断结束
GIC->C_EOIR = (GIC->C_EOIR & ~(0x1FF)) | irq_num;
// 调用中断处理函数
handler = function_table[irq_num];
handler(irq_num);
return;
}
- (3) CPSR的IRQ使能:通过C调用汇编指令
int enable_irq(void)
{
int status;
asm(
"mrs r0,cpsr\n"
"mov r1,#1\n"
"bic r0,r0,r1,lsl #7\n"
"msr cpsr,r0\n"
"mov %0,r0\n"
:"=r"(status)
:
:"r0","r1"
);
return status;
}
int disable_irq(void)
{
int status;
asm(
"mrs r0,cpsr\n"
"mov r1,#1\n"
"orr r0,r0,r1,lsl #7\n"
"msr cpsr,r0\n"
"mov %0,r0\n"
:"=r"(status)
:
:"r0","r1"
);
return status;
}
- (4)按下按键产生中断,查看中断处理函数的输出情况
void key_interrupt_test(void)
{
int irq_num = GPIO1_Combined_16_31_IRQn; // imx6ull参考手册chapter3查到gpio1_io26的中断号
key_irp_gpio_init();
request_irq(irq_num, key_interrupt_handler); // 初始化GIC中断控制器;同时注册中断处理函数
enable_irq(); // 使能cpsr的irq
test_timer(); // 调用led,GPIO_IO26高电平,循环开关;此时按下按键,管脚变为低电平,硬中断
}
注意:void do_irq(void)
是异常处理函数,在汇编irq异常处理中,通过指令bl do_irq
,跳转到此函数的地址,执行此函数。
资料:https://qmkd58bf2i.feishu.cn/docx/B7qHdnOd1oLjsYx8hZBcH5Qqndb