ARM64基础13:ARM64的异常处理之中断处理(以树莓派4采用的BCM2711芯片为例)

ARM64的中断属于异常模式的一种,对中断的处理流程跟上节的异常处理类似,走其中的一个分支;

树莓派4b的中断控制器分两种:传统的中断方式legacy interruptGIC(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

CNT_PS_IRQ:
这里采用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.打开树莓派b4中断控制器中和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, #S_FRAME_SIZE

	/*
	   保存通用寄存器x0~x29到栈框里pt_regs->x0~x29
	 */
	stp x0, x1, [sp, #16 *0]
	stp x2, x3, [sp, #16 *1]
	stp x4, x5, [sp, #16 *2]
	stp x6, x7, [sp, #16 *3]
	stp x8, x9, [sp, #16 *4]
	stp x10, x11, [sp, #16 *5]
	stp x12, x13, [sp, #16 *6]
	stp x14, x15, [sp, #16 *7]
	stp x16, x17, [sp, #16 *8]
	stp x18, x19, [sp, #16 *9]
	stp x20, x21, [sp, #16 *10]
	stp x22, x23, [sp, #16 *11]
	stp x24, x25, [sp, #16 *12]
	stp x26, x27, [sp, #16 *13]
	stp x28, x29, [sp, #16 *14]

	/* x21: 栈顶 的位置*/
	add     x21, sp, #S_FRAME_SIZE

	mrs     x22, elr_el1
	mrs     x23, spsr_el1

	/* 把lr保存到pt_regs->lr, 把sp保存到pt_regs->sp位置*/
	stp     lr, x21, [sp, #S_LR]
	/* 把elr_el1保存到pt_regs->pc中
	   把spsr_elr保存到pt_regs->pstate中*/
	stp     x22, x23, [sp, #S_PC]
	.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初始化

#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>

#define HZ 250
#define NSEC_PER_SEC    6000000000L

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");
}

中断服务程序:

#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>

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上模拟结果如下:

在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值