ARM64的中断属于异常模式的一种,对中断的处理流程跟上节的异常处理类似,走其中的一个分支;
树莓派4b的中断控制器分两种:传统的中断方式legacy interrupt和GIC(BCM2711支持GIC-400);
一个典型的ARM64中断处理流程:
树莓派4b上的硬件资源:
1.中断控制器:
GIC-400(默认)
传统的中断控制器(legacy interrupt controller)
2.树莓派4b支持多种中断源:
ARM Core N: ARM Core本身的中断源,例如Core里的generic timer;
ARMC: 可以被VPU和CPU访问的中断源,例如mailbox;
ARM_LOCAL: 只能被CPU访问的中断源,例如本地timer等;
3.树莓派4b的legacy中断控制器
实例:以ARM Core的generic timer产生中断信号
3.1 Cortex-A72支持4个ARM Core的generic timer
这里采用Nonsecure EL1 Physical Timer,演示中断处理过程;
查看ARMv8手册,第一个是EL1 TIMER的控制寄存器CNTP_CTL_EL0;
计数器寄存器CNTP_TVAL_EL0
Timer支持两种触发方式,参考armv8_D11.2.4;
3.2 ARM_LOCAL寄存器有:
4个IRQ_SOURCE寄存器,每个CPU一个,表示IRQ中断源状态;
4个FIQ_SOURCE寄存器,每个CPU一个,表示FIQ中断源状态;
4个TIMER_CNTRL寄存器,每个CPU一个,用来使能对应中断源;
1.查看BCM2711手册可知,ARM_LOCAL基地址为0x4c000000或0xff800000
对于IRQ_SOURCE寄存器,可以读取对应中断源状态;
TIMER_CNTRL寄存器用来使能中断响应
3.3 EL1的Nonsecure generic timer中断处理流程
初始化
1.初始化timer,设置cntp_ctl_el0寄存器的enable域使能;
2.给timer的TimeValue一个初值,设置cntp_tval_el0寄存器;
3.打开树莓派4b中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器的CNT_PNS_IRQ为1;
4.打开PSTATE寄存器中的IRQ中断总开关;
异常中断处理
5.Timer中断发生;
6.跳转到异常向量表的el1_irq1函数;
7.保存中断上下文(kernel_entry宏);
8.跳转到中断处理函数;
9.读取ARM_LOCAL中断状态寄存器IRQ_SOURCE0;
10.判断中断源,是否为CNT_PNS_IRQ中断;
11.如果是CNT_PNS_IRQ,重置TimerValue;
返回现场
12.返回到el1_irq函数;
13.恢复中断上下文;
14.返回中断现场
中断源的判断:
以一个稍微复杂的中断源为例,根据树莓派4b BCM2711手册:
由上图可知,最多分三级:
1.读SOURCEn中断寄存器;
2.若SOURCEn[8]置位,继续读取PENDING2寄存器;
3.若PENDING2[24]置位,则读PENDING0寄存器;
4.若PENDING2[25]置位,则读PENDING1寄存器;
可见当中断源数量增加到一定程度,中断处理程序将会变得十分繁杂,GIC控制器就是为解决这个问题而生,下篇将详细介绍;
3.4 中断现场(上下文)
3.4.1 中断上下文内容
中断发生瞬间,CPU状态包括:
PSTATE寄存器
PC值
SP值
X0~X30寄存器
Linux使用一个栈框数据结构来描述需要保存的中断现场;
3.4.2 保存中断现场
中断发生时,中断上下文保存到当前进程的内核栈里;
代码实现:
.macro kernel_entry | |
sub sp, sp, | |
/* | |
保存通用寄存器x0~x29到栈框里pt_regs->x0~x29 | |
*/ | |
stp x0, x1, [sp, | |
stp x2, x3, [sp, | |
stp x4, x5, [sp, | |
stp x6, x7, [sp, | |
stp x8, x9, [sp, | |
stp x10, x11, [sp, | |
stp x12, x13, [sp, | |
stp x14, x15, [sp, | |
stp x16, x17, [sp, | |
stp x18, x19, [sp, | |
stp x20, x21, [sp, | |
stp x22, x23, [sp, | |
stp x24, x25, [sp, | |
stp x26, x27, [sp, | |
stp x28, x29, [sp, | |
/* x21: 栈顶 的位置*/ | |
add x21, sp, | |
mrs x22, elr_el1 | |
mrs x23, spsr_el1 | |
/* 把lr保存到pt_regs->lr, 把sp保存到pt_regs->sp位置*/ | |
stp lr, x21, [sp, | |
/* 把elr_el1保存到pt_regs->pc中 | |
把spsr_elr保存到pt_regs->pstate中*/ | |
stp x22, x23, [sp, | |
.endm |
3.4.3 恢复中断现场
中断处理完成后,从内核栈中恢复中断现场;
代码实现:
.macro kernel_exit | |
/* 从pt_regs->pc中恢复elr_el1, | |
从pt_regs->pstate中恢复spsr_el1 | |
*/ | |
ldp x21, x22, [sp, #S_PC] // load ELR, SPSR | |
msr elr_el1, x21 // set up the return data | |
msr spsr_el1, x22 | |
ldp x0, x1, [sp, #16 * 0] | |
ldp x2, x3, [sp, #16 * 1] | |
ldp x4, x5, [sp, #16 * 2] | |
ldp x6, x7, [sp, #16 * 3] | |
ldp x8, x9, [sp, #16 * 4] | |
ldp x10, x11, [sp, #16 * 5] | |
ldp x12, x13, [sp, #16 * 6] | |
ldp x14, x15, [sp, #16 * 7] | |
ldp x16, x17, [sp, #16 * 8] | |
ldp x18, x19, [sp, #16 * 9] | |
ldp x20, x21, [sp, #16 * 10] | |
ldp x22, x23, [sp, #16 * 11] | |
ldp x24, x25, [sp, #16 * 12] | |
ldp x26, x27, [sp, #16 * 13] | |
ldp x28, x29, [sp, #16 * 14] | |
/* 从pt_regs->lr中恢复lr*/ | |
ldr lr, [sp, #S_LR] | |
add sp, sp, #S_FRAME_SIZE // restore sp | |
eret | |
.endm |
3.5 实例代码:
在树莓派上实现generic timer, 关键代码
timer初始化
static unsigned int val = NSEC_PER_SEC / HZ; | |
static int generic_timer_init(void) | |
{ | |
asm volatile( | |
"mov x0, #1\n" | |
"msr cntp_ctl_el0, x0" | |
: | |
: | |
: "memory"); | |
return 0; | |
} | |
static int generic_timer_reset(unsigned int val) | |
{ | |
asm volatile( | |
"msr cntp_tval_el0, %x[timer_val]" | |
: | |
: [timer_val] "r" (val) | |
: "memory"); | |
return 0; | |
} | |
static void enable_timer_interrupt(void) | |
{ | |
writel(CNT_PNS_IRQ, TIMER_CNTRL0); | |
} | |
void timer_init(void) | |
{ | |
generic_timer_init(); | |
generic_timer_reset(val); | |
enable_timer_interrupt(); | |
} | |
void handle_timer_irq(void) | |
{ | |
generic_timer_reset(val); | |
printk("Core0 Timer interrupt received\r\n"); | |
} |
中断服务程序:
void irq_handle(void) | |
{ | |
unsigned int irq = readl(ARM_LOCAL_IRQ_SOURCE0); | |
switch (irq) { | |
case (CNT_PNS_IRQ): | |
handle_timer_irq(); | |
break; | |
default: | |
printk("Unknown pending irq: %x\r\n", irq); | |
} | |
} |
主应用程序:
void kernel_main(void) | |
{ | |
uart_init(); | |
init_printk_done(); | |
uart_send_string("Welcome BenOS!\r\n"); | |
printk("printk init done\n"); | |
//my_ldr_test(); | |
my_data_process(); | |
//my_cmp_test(); | |
//other_test(); | |
print_func_name(0x800880); | |
//unsigned long val1 = 0,val2=0; | |
//val1 = macro_test1(3,5); | |
//val2 = macro_test2(3,5); | |
print_memmap(); | |
my_memcpy_asm_test(0x80000, 0x100000, 32); | |
my_memset_16bytes_asm(0x600000,0xaabbccdd,4096); | |
//test_asm_goto(1); | |
//trigger_alignment(); | |
printk("init done\n"); | |
timer_init(); //初始化timer | |
raw_local_irq_enable();//启动timer | |
my_ops_test(); | |
test_sysregs(); | |
int count=0; | |
while (1) { | |
//uart_send(uart_recv()); | |
printk("printk while...%ds\n",count++); | |
delay_s(1); | |
} | |
} |
3.6在qemu上模拟结果如下: