• 【精读Uboot】异常向量的设置


    1、异常基础知识

    对于ARM64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的,具有更高执行权利(EL3)的异常处理程序(也叫exception handler)。执行exception handler可以进行异常的处理,从而让系统平滑的运行。exception handler执行完毕之后,需要返回发生异常的现场。

    异常分类

    虽然异常有很多种,但是基本可以分成两类,异步异常(asynchronous exception)和同步异常(synchronous exception)。同步异常又可以细分成两个类别,一种我们称之为synchronous abort,例如未定义的指令、data abort、prefetch instruction abort、SP未对齐异常,debug exception等等。还有一种是正常指令执行造成的,包括SVC/HVC/SMC指令,这些指令的使命就是产生异常。

    在_start.S中,我们将vectors的地址设置进了了vbar_el3寄存器,也就让芯片知道了异常发生时,应该跳转到这个地址执行异常处理。异常向量表许多不同类型的异常和中断,如同步异常(synchronous)、IRQ中断、FIQ中断和SError错误。

    2、vectors

    异常向量表中的每个条目是128字节对齐的,整个异常向量表必须按2K字节对齐。使用stp指令将x29x30寄存器的值保存到栈上,然后通过bl指令调用_exception_entry函数。接下来,通过bl指令调用do_bad_sync函数来处理同步异常。最后,通过b指令跳转到exception_exit标签。

    .align	11
    	.globl	vectors
    vectors:
    	.align	7		/* Current EL Synchronous Thread */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_bad_sync
    	b	exception_exit
    	
    	.align	7		/* Current EL IRQ Thread */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_bad_irq
    	b	exception_exit
    
    	.align	7		/* Current EL FIQ Thread */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_bad_fiq
    	
    	.align	7		/* Current EL Error Thread */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_bad_error
    	b	exception_exit
    	
    	.align	7		 /* Current EL (SP_ELx) Synchronous Handler */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_sync
    	b	exception_exit
    
    	.align	7		 /* Current EL (SP_ELx) IRQ Handler */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_irq
    	b	exception_exit
    
    	.align	7		 /* Current EL (SP_ELx) FIQ Handler */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_fiq
    	b	exception_exit
    
    	.align	7		 /* Current EL (SP_ELx) Error Handler */
    	stp	x29, x30, [sp, #-16]!
    	bl	_exception_entry
    	bl	do_error
    	b	exception_exit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    为什么需要按照2K对齐?

    2K对齐后,地址的最低11位为零。这也就和vbar_el3的寄存器定义吻合上了。只有2K对齐才能将正确的异常向量地址设置进vbar_el3寄存器。

    image-20230707165232084

    为什么每个条目是128字节对齐?

    下表是ARM手册中的一个异常向量表定义。每一项与这个基址有一个定义好了的偏移量,例如第二个偏移量是0x80,所有偏移量之间都相差了128字节。每个表有16项,每项128字节(32条指令)大小。程序中必须严格按照这个顺序设置异常向量。

    image-20230707184721730

    参考《aarch64_exception_and_interrupt_handling_100933_0100_en.pdf》

    3、_exception_entry

    _exception_entry的作用是保存异常发生时的寄存器信息。

    _exception_entry:
    	stp	x27, x28, [sp, #-16]!
    	stp	x25, x26, [sp, #-16]!
    	stp	x23, x24, [sp, #-16]!
    	stp	x21, x22, [sp, #-16]!
    	stp	x19, x20, [sp, #-16]!
    	stp	x17, x18, [sp, #-16]!
    	stp	x15, x16, [sp, #-16]!
    	stp	x13, x14, [sp, #-16]!
    	stp	x11, x12, [sp, #-16]!
    	stp	x9, x10, [sp, #-16]!
    	stp	x7, x8, [sp, #-16]!
    	stp	x5, x6, [sp, #-16]!
    	stp	x3, x4, [sp, #-16]!
    	stp	x1, x2, [sp, #-16]!
    	b	_save_el_regs			/* jump to the second part */
    	
    _save_el_regs:
    	/* Could be running at EL3/EL2/EL1 */
    	switch_el x11, 3f, 2f, 1f
    3:	mrs	x1, esr_el3
    	mrs	x2, elr_el3
    	b	0f
    2:	mrs	x1, esr_el2
    	mrs	x2, elr_el2
    	b	0f
    1:	mrs	x1, esr_el1
    	mrs	x2, elr_el1
    0:
    	stp	x2, x0, [sp, #-16]!
    	mov	x0, sp
    	ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    首先将x1x28的寄存器值保存到栈指针sp中,每个寄存器对的值都通过[sp, #-16]!的方式存储,这会将栈指针(sp)向下递减16字节,然后将寄存器的值存储到新的栈位置。然后通过b指令跳转到_save_el_regs标签,进入异常处理程序的第二部分。根据当前的异常等级(EL)选择执行不同的分支。通过switch_el指令,根据x11寄存器的值,选择跳转到不同的分支,保存对应的ESRELR寄存器的值。

    如果当前处于在EL3异常等级(Hypervisor或Secure Monitor模式)下,程序跳转到3标签。首先使用mrs指令将ESR_EL3的值加载到x1寄存器中,然后将ELR_EL3的值加载到x2寄存器中。之后,通过b指令跳转到0标签。然后指令stp x2, x0, [sp, #-16]!x2x0的值保存到栈帧中。这里的[sp, #-16]!将栈指针(SP)向下递减16个字节,然后将x2x0的值存储到新的栈位置。

    接下来的mov x0, sp指令将栈指针(SP)的值加载到x0寄存器中。这是为了将栈指针的值传递给上层的异常处理程序或返回给调用者。至此,_save_el_regs完成了保存el寄存器的工作,并返回调用点继续执行。

    4、do_bad_irq

    efi_restore_gd针对支持efi的场景,目的是恢复gd,然后打印arm64的寄存器内容,陷入panic进行reset。如果使用了hang()函数,程序则会陷入无限循环。

    arch/arm/lib/interrupts_64.c
    void do_bad_irq(struct pt_regs *pt_regs, unsigned int esr)
    {
    	efi_restore_gd();
    	printf("Bad mode in \"Irq\" handler, esr 0x%08x\n", esr);
    	show_regs(pt_regs);
    	show_efi_loaded_images(pt_regs);
    	panic("Resetting CPU ...\n");
    }
    
    static void panic_finish(void)
    {
    	putc('\n');
    #if defined(CONFIG_PANIC_HANG)
    	hang();
    #else
    	udelay(100000);	/* allow messages to go out */
    	do_reset(NULL, 0, 0, NULL);
    #endif
    	while (1)
    		;
    }
    
    void panic_str(const char *str)
    {
    	puts(str);
    	panic_finish();
    }
    
    void panic(const char *fmt, ...)
    {
    #if CONFIG_IS_ENABLED(PRINTF)
    	va_list args;
    	va_start(args, fmt);
    	vprintf(fmt, args);
    	va_end(args);
    #endif
    	panic_finish();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    reset过程

    do_reset 函数负责输出信息、延时一段时间、禁用中断,然后调用 reset_cpu 函数执行系统重启操作。reset_cpu 函数通过触发 watchdog 定时器过期来实现系统重启。在 imx_watchdog_expire_now 函数中,会根据传入的参数配置 watchdog 控制寄存器,并连续写入三次以确保配置生效。最后,在写入配置值之后进入一个无限循环,程序会停留在此处,直到系统重启。

    int do_reset(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
    {
    	puts ("resetting ...\n");
    
    	mdelay(50);				/* wait 50 ms */
    	disable_interrupts(); //空函数
    	reset_cpu();
    	return 0;
    }
    void __attribute__((weak)) reset_cpu(void)
    {
    	struct watchdog_regs *wdog = (struct watchdog_regs *)WDOG1_BASE_ADDR;
    
    	imx_watchdog_expire_now(wdog, false);
    }
    static void imx_watchdog_expire_now(struct watchdog_regs *wdog, bool ext_reset)
    {
    	u16 wcr = WCR_WDE;
    
    	if (ext_reset)
    		wcr |= WCR_SRS; /* do not assert internal reset */
    	else
    		wcr |= WCR_WDA; /* do not assert external reset */
    
    	/* Write 3 times to ensure it works, due to IMX6Q errata ERR004346 */
    	writew(wcr, &wdog->wcr);
    	writew(wcr, &wdog->wcr);
    	writew(wcr, &wdog->wcr);
    
    	while (1) {
    		/*
    		 * spin before reset
    		 */
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    5、exception_exit

    代码从 exception_exit 标签处开始执行。ldp x2, x0, [sp],#16 指令将栈顶的两个双字(16字节)数据(x2 和 x0)加载到寄存器中,并将栈指针自增16个字节。switch_el x11, 3f, 2f, 1f 指令根据当前异常的处理级别选择跳转目标。x11 寄存器的值决定了跳转的目标标签。如果异常处理级别为 EL3,则跳转到标签 3 执行。在 3 处,通过 msr elr_el3, x2 指令将 x2 寄存器中的值写入 ELR_EL3 寄存器,然后跳转到 _restore_regs 过程。在 _restore_regs 过程中,通过一系列的 ldp 指令,将之前保存在栈上的通用寄存器依次加载回来。每个 ldp 指令将栈顶的两个双字(16字节)数据加载到指定的寄存器中,并将栈指针自增16个字节。最后,通过 eret 指令执行异常返回,将程序控制权恢复到异常发生时的指令位置,从而结束异常处理过程。

    exception_exit:
    	ldp	x2, x0, [sp],#16
    	switch_el x11, 3f, 2f, 1f
    3:	msr	elr_el3, x2
    	b	_restore_regs
    2:	msr	elr_el2, x2
    	b	_restore_regs
    1:	msr	elr_el1, x2
    	b	_restore_regs
    
    _restore_regs:
    	ldp	x1, x2, [sp],#16
    	ldp	x3, x4, [sp],#16
    	ldp	x5, x6, [sp],#16
    	ldp	x7, x8, [sp],#16
    	ldp	x9, x10, [sp],#16
    	ldp	x11, x12, [sp],#16
    	ldp	x13, x14, [sp],#16
    	ldp	x15, x16, [sp],#16
    	ldp	x17, x18, [sp],#16
    	ldp	x19, x20, [sp],#16
    	ldp	x21, x22, [sp],#16
    	ldp	x23, x24, [sp],#16
    	ldp	x25, x26, [sp],#16
    	ldp	x27, x28, [sp],#16
    	ldp	x29, x30, [sp],#16
    	eret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 相关阅读:
    SQLite自动建库建表
    Snort搭建以及规则编写
    Watermelon Book(二)线性模型
    关于产品和消费的思考
    关于无障碍适配的笔记
    【Linux精讲系列】——yum软件包管理
    Element_文件上传&&多个文件上传
    高质量数据is all you need:Textbooks Are All You Need论文笔记
    云计算-期末复习题-选择/判断/填空/简答(1)
    微信小程序发布流程
  • 原文地址:https://blog.csdn.net/qq_38131812/article/details/132751942