Linux:ARM64 中断处理简析

文章目录

  • [1. 前言](#1. 前言)
  • [2. ARM64 中断处理流程扼要](#2. ARM64 中断处理流程扼要)
    • [2.1 ARM64 的 CPU 异常等级 和 运行态](#2.1 ARM64 的 CPU 异常等级 和 运行态)
    • [2.2 ARM64 中断向量表配置 和 中断处理流程](#2.2 ARM64 中断向量表配置 和 中断处理流程)
    • [2.3 其它:RESET 异常 和 系统调用](#2.3 其它:RESET 异常 和 系统调用)

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. ARM64 中断处理流程扼要

2.1 ARM64 的 CPU 异常等级 和 运行态

ARM64 没有像 ARM32 的那么多个 CPU 模式,ARM64 CPU 当前位于 EL0~EL3 的异常等级之一,这有点类似于 x86 架构下的 RING0~RING3。ARM64 CPU 的 ELx 异常等级的 x 越大,权限越高。

  • EL0:非特权模式,通常用于运行应用程序
  • EL1:特权模式,通常用于运行 OS 内核
  • EL2:Non-secure 虚拟化支持
  • EL3:Secure monitor 模式,用于 Non-secure 和 Secure 状态切换

ARM64 架构并不强制要求实现所有的异常等级,但 EL0,EL1 是必须要实现的。

另外,为了在 ARM64 下可以运行 ARM32 程序,所以 ARM64 CPU 有一个 AArch32 状态,上面的中断向量表 vectors 也定义了 AArch32 的中断向量表。

2.2 ARM64 中断向量表配置 和 中断处理流程

ARM64 向量的排列定义如下:

看下 ARM64 Linux 中断向量表的定义:

c 复制代码
// arch/arm64/kernel/entry.S

/*
 * Exception vectors.
 */
	.pushsection ".entry.text", "ax"

	.align	11
ENTRY(vectors)
	// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
	// 使用栈指针 SP_EL0
	kernel_ventry	1, sync_invalid			// Synchronous EL1t
	kernel_ventry	1, irq_invalid			// IRQ EL1t
	kernel_ventry	1, fiq_invalid			// FIQ EL1t
	kernel_ventry	1, error_invalid		// Error EL1t

	// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
	// 使用栈指针 SP_EL1
	kernel_ventry	1, sync				// Synchronous EL1h
	kernel_ventry	1, irq				// IRQ EL1h
	kernel_ventry	1, fiq_invalid			// FIQ EL1h
	kernel_ventry	1, error_invalid		// Error EL1h

	// AArch64 态, EL0异常等级(即用户态) 的 4 种类型异常 的 中断向量入口
	kernel_ventry	0, sync				// Synchronous 64-bit EL0
	kernel_ventry	0, irq				// IRQ 64-bit EL0
	kernel_ventry	0, fiq_invalid			// FIQ 64-bit EL0
	kernel_ventry	0, error_invalid		// Error 64-bit EL0

	// AArch32 态, EL0异常等级(即用户态) 的 4 种类型异常 的 中断向量入口
#ifdef CONFIG_COMPAT
	kernel_ventry	0, sync_compat, 32		// Synchronous 32-bit EL0
	kernel_ventry	0, irq_compat, 32		// IRQ 32-bit EL0
	kernel_ventry	0, fiq_invalid_compat, 32	// FIQ 32-bit EL0
	kernel_ventry	0, error_invalid_compat, 32	// Error 32-bit EL0
#else
	kernel_ventry	0, sync_invalid, 32		// Synchronous 32-bit EL0
	kernel_ventry	0, irq_invalid, 32		// IRQ 32-bit EL0
	kernel_ventry	0, fiq_invalid, 32		// FIQ 32-bit EL0
	kernel_ventry	0, error_invalid, 32		// Error 32-bit EL0
#endif
END(vectors)

每个异常属于以下 4 种类型之一:

中断向量为每个异常等级 ELx 的上图中每一种异常类型定义一个处理入口,如 vectors 表中为 EL1 的 Synchronous exception,IRQ,FIQ,SError 定义了如下 4 个异常类型处理入口:

c 复制代码
// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
	// 使用栈指针 SP_EL0
	kernel_ventry	1, sync_invalid			// Synchronous EL1t
	kernel_ventry	1, irq_invalid			// IRQ EL1t
	kernel_ventry	1, fiq_invalid			// FIQ EL1t
	kernel_ventry	1, error_invalid		// Error EL1t

接下来是设置中断向量表地址到 vbar_el1 寄存器:

c 复制代码
// arch/arm64/kernel/head.S

__primary_switched: // BOOT CPU
	...
	// 配置 EL1 异常向量表 (虚拟地址 到 vbar_el1 寄存器)
	adr_l	x8, vectors			// load VBAR_EL1 with virtual
	msr	vbar_el1, x8			// vector table address
	isb
	
	...

	b	start_kernel

__secondary_switched: // 非 BOOT CPU
	// 配置 EL1 异常向量表 (虚拟地址 到 vbar_el1 寄存器)
	adr_l	x5, vectors
	msr	vbar_el1, x5
	isb

	...

	b	secondary_start_kernel
ENDPROC(__secondary_switched)

当发生中断时,CPU 进入对应的异常等级,然后定位到对应的异常类型入口进行处理。本文只简要的讨论下 ARM64 Linux 内核下 IRQ 异常的处理过程,对其它异常感兴趣的读者,可自行阅读相关源码。

看看 ARM64 Linux内核态(EL1) IRQ 异常的处理流程:

c 复制代码
.align	6
el1_irq:
	kernel_entry 1
	enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif

	irq_handler // IRQ 中断处理

	// 处理完 IRQ 异常后,检测是否有挂起的抢占需求,如果有则进行调度抢占
#ifdef CONFIG_PREEMPT
	ldr	w24, [tsk, #TSK_TI_PREEMPT]	// get preempt count
	cbnz	w24, 1f				// preempt count != 0
	ldr	x0, [tsk, #TSK_TI_FLAGS]	// get flags
	tbz	x0, #TIF_NEED_RESCHED, 1f	// needs rescheduling?
	bl	el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif
	kernel_exit 1
ENDPROC(el1_irq)

/*
 * Interrupt handling.
 */
	.macro	irq_handler
	ldr_l	x1, handle_arch_irq // gic_handle_irq()
	mov	x0, sp
	irq_stack_entry
	blr	x1
	irq_stack_exit
	.endm

假定使用的 GIC 中断处理芯片,则进入 gic_handle_irq() 处理 IRQ 中断,细节可参考 GIC 中断处理流程

用户态(EL0) 的 IRQ 异常处理流程类似,只不过没有中断处理完成后抢占检测处理:

c 复制代码
.align	6
el0_irq:
	kernel_entry 0
el0_irq_naked:
	enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif

	ct_user_exit
#ifdef CONFIG_HARDEN_BRANCH_PREDICTOR
	tbz	x22, #55, 1f
	bl	do_el0_irq_bp_hardening
1:
#endif
	irq_handler

#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif
	b	ret_to_user
ENDPROC(el0_irq)

其中的 kernel_entry,kernel_exit,ret_to_user 主要是做上下文保存和恢复操作。

2.3 其它:RESET 异常 和 系统调用

ARM64 特殊的硬件 RESET 异常是比较特殊的:

CPU 的硬件复位轮不到 Linux 来处理,因为这一般是 SoC 厂家固化 ROM 的工作,了解下即可。

最后,ARM32 使用 SWI 指令实现系统调用,在 ARM64 则切换为 SVC(Supervisor Call),HVC(Hypervisor Call)。ARM64 系统调用概要流程为:

bash 复制代码
             触发 sync 异常
发出 SVC 指令 ------------> 进入 sync 异常处理入口 ---> 调用系统调用
c 复制代码
/*
 * EL0 mode handlers.
 */
	.align	6
el0_sync:
	kernel_entry 0
	mrs	x25, esr_el1			// read the syndrome register
	lsr	x24, x25, #ESR_ELx_EC_SHIFT	// exception class
	cmp	x24, #ESR_ELx_EC_SVC64		// SVC in 64-bit state
	b.eq	el0_svc // AArch64 系统调用
	...

#ifdef CONFIG_COMPAT
	.align	6
el0_sync_compat:
	kernel_entry 0, 32
	mrs	x25, esr_el1			// read the syndrome register
	lsr	x24, x25, #ESR_ELx_EC_SHIFT	// exception class
	cmp	x24, #ESR_ELx_EC_SVC32		// SVC in 32-bit state
	b.eq	el0_svc_compat
	...
el0_svc_compat:
	/*
	 * AArch32 syscall handling
	 */
	adrp	stbl, compat_sys_call_table	// load compat syscall table pointer
	mov	wscno, w7			// syscall number in w7 (r7)
	mov     wsc_nr, #__NR_compat_syscalls
	b	el0_svc_naked // AArch32 系统调用

	.align	6
el0_irq_compat:
	kernel_entry 0, 32
	b	el0_irq_naked
#endif

	...
	b	ret_to_user
ENDPROC(el0_sync)

/*
 * SVC handler.
 */
	.align	6
el0_svc:
	adrp	stbl, sys_call_table		// load syscall table pointer
	mov	wscno, w8			// syscall number in w8
	mov	wsc_nr, #__NR_syscalls
el0_svc_naked:					// compat entry point
	stp	x0, xscno, [sp, #S_ORIG_X0]	// save the original x0 and syscall number
	enable_dbg_and_irq
	ct_user_exit 1

	ldr	x16, [tsk, #TSK_TI_FLAGS]	// check for syscall hooks
	tst	x16, #_TIF_SYSCALL_WORK
	b.ne	__sys_trace
	cmp     wscno, wsc_nr			// check upper syscall limit
	b.hs	ni_sys
	mask_nospec64 xscno, xsc_nr, x19	// enforce bounds for syscall number
	ldr	x16, [stbl, xscno, lsl #3]	// address in the syscall table
	// 调用系统调用 #nr
	blr	x16				// call sys_* routine
	b	ret_fast_syscall
ni_sys:
	mov	x0, sp
	bl	do_ni_syscall
	b	ret_fast_syscall
ENDPROC(el0_svc)
相关推荐
小生不才yz1 小时前
【Makefile 专家之路 | 函数篇】11. 终极奥义:eval 函数——动态生成规则的“核武器”
linux
皮卡蛋炒饭.2 小时前
进程得控制
linux·运维·服务器
YMWM_2 小时前
Install pyrealsense2 on the jetson thor
linux·realsense2
BestOrNothing_20153 小时前
(3)Ubuntu 22.04 双系统安装全过程记录
linux·ubuntu22.04·双系统安装
寂柒4 小时前
Linux——基础IO
linux
杨云龙UP4 小时前
Oracle ASM磁盘组空间分配与冗余理解
linux·运维·数据库·sql·oracle
朽棘不雕5 小时前
Linux权限
linux
minji...5 小时前
Linux 库制作与原理(三)深入动静态链接原理
linux·运维·服务器·c++
bukeyiwanshui5 小时前
Linux实践
linux·运维·服务器