文章目录
1、ARM的异常向量表基地址寄存器--VBAR
1.1、armv8 : VBAR寄存器
1.2、armv7 : VBAR寄存器
2、ARM的异常向量表的定义
2.1 armv8 :异常向量表offset的定义
2.2 armv8 : 在linux kernel中异常向量表的实现定义
2.3 armv8 : 在ATF中异常向量表的实现定义
2.4 armv8 : 异常向量表总结和示例:
2.5 armv7 :异常向量表offset的定义
2.6 armv7 : 在linux kernel中异常向量表的实现定义
3、总结:
1、ARM的异常向量表基地址寄存器–VBAR
1.1、armv8 : VBAR寄存器
1.2、armv7 : VBAR寄存器
2、ARM的异常向量表的定义
2.1 armv8 :异常向量表offset的定义
实际上有四组表,每组表有四个异常入口,分别对应同步异常,IRQ,FIQ和出错异常。
如果发生异常并不会导致exception level切换,并且使用的栈指针是SP_EL0,那么使用第一组异常向量表。
如果发生异常并不会导致exception level切换,并且使用的栈指针是SP_EL1/2/3,那么使用第二组异常向量表。
如果发生异常会导致exception level切换,并且比目的exception level低一级的exception
level运行在AARCH64模式,那么使用第三组异常向量表。
如果发生异常会导致exception level切换,并且比目的exception level低一级的exception
level运行在AARCH32模式,那么使用第四组异常向量表。
另外我们还可以看到的一点是,每一个异常入口不再仅仅占用4bytes的空间,而是占用0x80 bytes空间,也就是说,每一个异常入口可以放置多条指令,而不仅仅是一条跳转指令
2.2 armv8 : 在linux kernel中异常向量表的实现定义
我们再来看在linux kernel中定义的异常向量表
.align 11
ENTRY(vectors)
(1)
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
(2)
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
(3)
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
(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)
第(1)段对应的是第一行向量表、第(2)段对应的是第二行向量表、第(3)段对应的是第三行向量表、第四段对应的是第一行向量表.
带invalid后缀的,都是未实现的。然后我们抽出实现了的部分,仅4行:
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
他们对应的函数分别是:
el1_sync //在kernel mode中调用svc指令,触发同步异常
el1_irq //在kernel mode中触发了irq异步异常
el0_sync //在user mode中调用svc指令,触发同步异常
el0_irq //在user mode中触发了irq异步异常
2.3 armv8 : 在ATF中异常向量表的实现定义
在ATF的代码中,在不同的阶段有着不同的异常向量表:
func bl1_entrypoint
......
el3_entrypoint_common \
_set_endian=1 \
_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS \
_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU \
_init_memory=1 \
_init_c_runtime=1 \
_exception_vectors=bl1_exceptions
func bl2_entrypoint
el3_entrypoint_common \
_set_endian=0 \
_warm_boot_mailbox=0 \
_secondary_cold_boot=0 \
_secondary_cpu = 0 \
_init_memory=0 \
_init_c_runtime=1 \
_exception_vectors=bl2_vector
func bl31_entrypoint
......
el3_entrypoint_common \
_set_endian=0 \
_warm_boot_mailbox=0 \
_secondary_cold_boot=0 \
_init_memory=0 \
_init_c_runtime=1 \
_exception_vectors=runtime_exceptions
我们常说的ATF中的向量表,其实就是bl31之后使用runtime_exceptions向量表,下面重点介绍下
(注意 : 带unhandled的都是未实现的)
vector_entry sync_exception_sp_el0
b report_unhandled_exception
check_vector_size sync_exception_sp_el0
vector_entry irq_sp_el0
b report_unhandled_interrupt
check_vector_size irq_sp_el0
vector_entry fiq_sp_el0
b report_unhandled_interrupt
check_vector_size fiq_sp_el0
vector_entry serror_sp_el0
b report_unhandled_exception
check_vector_size serror_sp_el0
vector_entry sync_exception_sp_elx
b report_unhandled_exception
check_vector_size sync_exception_sp_elx
vector_entry irq_sp_elx
b report_unhandled_interrupt
check_vector_size irq_sp_elx
vector_entry fiq_sp_elx
b report_unhandled_interrupt
check_vector_size fiq_sp_elx
vector_entry serror_sp_elx
b report_unhandled_exception
check_vector_size serror_sp_elx
vector_entry sync_exception_aarch64
handle_sync_exception
check_vector_size sync_exception_aarch64
vector_entry irq_aarch64
handle_interrupt_exception irq_aarch64
check_vector_size irq_aarch64
vector_entry fiq_aarch64
handle_interrupt_exception fiq_aarch64
check_vector_size fiq_aarch64
vector_entry serror_aarch64
b report_unhandled_exception
check_vector_size serror_aarch64
vector_entry sync_exception_aarch32
handle_sync_exception
check_vector_size sync_exception_aarch32
vector_entry irq_aarch32
handle_interrupt_exception irq_aarch32
check_vector_size irq_aarch32
vector_entry fiq_aarch32
handle_interrupt_exception fiq_aarch32
check_vector_size fiq_aarch32
vector_entry serror_aarch32
b report_unhandled_exception
check_vector_size serror_aarch32
2.4 armv8 : 异常向量表总结和示例:
以异步异常irq为例
(1)、当irq产生,如果该irq被target到了EL3,那么将使用VBAR_EL3寄存器中的基地址.
我们知道armv8有四组异常向量表,对应表格四行。然后再看使用哪组表:
cpu运行在EL0时产生了irq, 切未发生EL级别切换(中断需被target到EL0),使用第一组表,这个条件不会存在.
cpu是在EL3时产生的irq, 未发生EL级别切换,使用第二组表,跳转到VBAR_EL3 + 0x280处,对应的irq_sp_elx函数
cpu运行在aarch64级别,在EL1/EL2时产生的irq,发生了EL级别切换,使用第三组表,跳转到VBAR_EL3 + 0x480处,对应的irq_aarch64函数
cpu运行在aarch32级别,在EL1/EL2时产生的irq,发生了EL级别切换,使用第四组表,跳转到VBAR_EL3 + 0x680处,对应的irq_aarch32函数
(2)、当irq产生,如果该irq被target到了EL1,那么将使用VBAR_EL1寄存器中的基地址.
我们知道armv8有四组异常向量表,对应表格四行。然后再看使用哪组表:
cpu运行在EL0时产生了irq, 切未发生EL级别切换(中断需被target到EL0),使用第一组表,这个条件不会存在.
cpu是在EL1时产生的irq, 未发生EL级别切换,使用第二组表,跳转到VBAR_EL1 + 0x280处,对应的el1_irq函数
cpu运行在aarch64级别,在EL0时产生的irq,发生了EL级别切换,使用第三组表,跳转到VBAR_EL1 + 0x480处,对应的el0_irq函数
cpu运行在aarch32级别,在EL0时产生的irq,发生了EL级别切换,使用第四组表,跳转到VBAR_EL1 + 0x680处,对应的irq_invalid函数,也就是未实现
2.5 armv7 :异常向量表offset的定义
2.6 armv7 : 在linux kernel中异常向量表的实现定义
.section .stubs, "ax", %progbits
__stubs_start:
@ This must be the first word
.word vector_swi
.section .vectors, "ax", %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
3、总结:
1、在armv7下使用的是data abort、prefetch abort、undefined instruction,在armv8下使用的是SError.
2、在linux kernel中,armv7体系下均已实现data abort、prefetch abort、undefined instruction异常处理函数,在linux kernel的armv8体系下,没有实现SError异常处理
3、在linux kernel的armv8体系中,未实现fiq函数
4、
在armv7的向量表offset中,每一个异常入口都是占4 bytes,是这样排的:0x1c 0x18 0x10 0x0c 0x08 0x04
在armv8的向量表offset中,每一个异常入口都是占0x80bytes,是这样排的00 0x80 0x100 0x180 0x200 0x280…
那么我们在linux kernel中定义的向量表,是怎样和上面的offset对应上的?
W(b) vector_irq
W(b) vector_fiq
5、在armv7中,VBAR是banked的,在linux/tee各有一份拷贝. 在armv7中,没有gic的cpu interface。所以armv7的芯片只能使用gicv2.
在gicv2中,FIQ表示安全中断,给TEE用的,IRQ表示非安全中断,给Linux用的.
当cpu在REE(linux)执行时,来了一个非安全中断(linux中断、IRQ), 那么cpu陷入异常,跳转到 "normal VBAR + irq offset“ 处, 也就是linux irq中断处理函数
当cpu在REE(linux)执行时,来了一个安全中断(tee中断、FIQ),那么cpu陷入异常,跳转到 "normal VBAR + fiq offset“ 处,也就是linux fiq中断处理函数
当cpu在TEE(tee)执行时,来了一个安全中断(TEE中断、FIQ), 那么cpu陷入异常,跳转到 "secure VBAR + fiq offset“ 处, 也就是tee irq中断处理函数
当cpu在TEE(tee)执行时,来了一个非安全中断(linux中断、irq),那么cpu陷入异常,跳转到 "secure VBAR + irq offset“ 处,也就是tee fiq中断处理函数, 在该函数中会主动将cpu切换到linux进行处理.
6、如果在某一时刻,将发送给cpu0的IPI_RESCHEDULE(SGI=2)中断屏蔽了,过了一会再恢复. 这样会对linux系统有影响吗?
内核的中断屏蔽,是将cpu的PSTATE.I置0了。local_disable_irq后,gic再产生的中断,ARM Core不会去处理,也就不会清除gic寄存器中的中断状态. 等到 local_enable_irq后, 该中断还会再送给ARM Core. 所以这种情况中断不会丢失…
所以在某一时刻屏蔽了IPI_RESCHEDULE,也只是屏蔽,也没有去清gic中相关寄存器。所以等到恢复屏蔽后,应该也不会丢中断