Exception vector table and vbar in optee3.14_ El1. Introduction to interrupt implementation
★★★ personal blog guide home page - click here ★★★
.
explain:
By default, this article describes the ARMV8-aarch64 architecture, optee version 3.14
1. Introduction to the exception vector table of armv8-aarch64
We can see that there are actually four groups of tables. Each table has four exception entries, corresponding to synchronization exceptions, IRQ, FIQ and serror.
- If there is no exception level switch after an exception occurs, and the stack pointer used before the exception occurs is SP_EL0, then use the first set of exception vector tables.
- If there is no exception level switch after an exception occurs, and the stack pointer used before the exception occurs is SP_EL1/2/3, then use the second set of exception vector table.
- If an exception occurs, the exception level is switched, and the exception before the exception occurs
If level runs in AARCH64 mode, the third set of exception vector tables is used. - If an exception occurs, the exception level is switched, and the exception before the exception occurs
If level runs in AARCH32 mode, the fourth set of exception vector table is used.
In addition, we can also see that each exception entry no longer occupies only 4 bytes, but 0x80 bytes, that is, each exception entry can place multiple instructions, not just a jump instruction
2. Vbar for armv8_ ELX register
armv8 defines VBAR_EL1,VBAR_EL2,VBAR_EL3 three base address registers
3. Implementation of optee exception vector table
(optee_os/core/arch/arm/kernel/thread_a64.S) #define INV_INSN 0 FUNC thread_excp_vect , : align=2048 /* ----------------------------------------------------- * EL1 with SP0 : 0x0 - 0x180 * ----------------------------------------------------- */ .balign 128, INV_INSN el1_sync_sp0: store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 3 b el1_sync_abort check_vector_size el1_sync_sp0 .balign 128, INV_INSN el1_irq_sp0: store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 3 b elx_irq check_vector_size el1_irq_sp0 .balign 128, INV_INSN el1_fiq_sp0: store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 3 b elx_fiq check_vector_size el1_fiq_sp0 .balign 128, INV_INSN el1_serror_sp0: b el1_serror_sp0 check_vector_size el1_serror_sp0 /* ----------------------------------------------------- * Current EL with SP1: 0x200 - 0x380 * ----------------------------------------------------- */ .balign 128, INV_INSN el1_sync_sp1: b el1_sync_sp1 check_vector_size el1_sync_sp1 .balign 128, INV_INSN el1_irq_sp1: b el1_irq_sp1 check_vector_size el1_irq_sp1 .balign 128, INV_INSN el1_fiq_sp1: b el1_fiq_sp1 check_vector_size el1_fiq_sp1 .balign 128, INV_INSN el1_serror_sp1: b el1_serror_sp1 check_vector_size el1_serror_sp1 /* ----------------------------------------------------- * Lower EL using AArch64 : 0x400 - 0x580 * ----------------------------------------------------- */ .balign 128, INV_INSN el0_sync_a64: restore_mapping mrs x2, esr_el1 mrs x3, sp_el0 lsr x2, x2, #ESR_EC_SHIFT cmp x2, #ESR_EC_AARCH64_SVC b.eq el0_svc b el0_sync_abort check_vector_size el0_sync_a64 .balign 128, INV_INSN el0_irq_a64: restore_mapping b elx_irq check_vector_size el0_irq_a64 .balign 128, INV_INSN el0_fiq_a64: restore_mapping b elx_fiq check_vector_size el0_fiq_a64 .balign 128, INV_INSN el0_serror_a64: b el0_serror_a64 check_vector_size el0_serror_a64 /* ----------------------------------------------------- * Lower EL using AArch32 : 0x0 - 0x180 * ----------------------------------------------------- */ .balign 128, INV_INSN el0_sync_a32: restore_mapping mrs x2, esr_el1 mrs x3, sp_el0 lsr x2, x2, #ESR_EC_SHIFT cmp x2, #ESR_EC_AARCH32_SVC b.eq el0_svc b el0_sync_abort check_vector_size el0_sync_a32 .balign 128, INV_INSN el0_irq_a32: restore_mapping b elx_irq check_vector_size el0_irq_a32 .balign 128, INV_INSN el0_fiq_a32: restore_mapping b elx_fiq check_vector_size el0_fiq_a32 .balign 128, INV_INSN el0_serror_a32: b el0_serror_a32 check_vector_size el0_serror_a32
(1),check_vector_size
check_ vector_ In fact, size is to check the instruction size in the exception vector, which cannot be 32 * 4 = 128 bytes, because the address range in each offset of the exception vector defined by armv8-arch64 is 128 bytes
.macro check_vector_size since .if (. - \since) > (32 * 4) .error "Vector exceeds 32 instructions" .endif .endm
(2) , 128 byte exception vector
balign 128 tells the assembly code that the next function definition is 128 bytes. This is also consistent with the address range of the exception vector defined by armv8-arch64
.balign 128, INV_INSN
(3) Summary of exception vector implementation
group | Anomaly vector | Processed functions | Determine whether to implement |
---|---|---|---|
first group | el1_sync_sp0 | b el1_sync_abort | Y |
first group | el1_irq_sp0 | b elx_irq | Y |
first group | el1_fiq_sp0 | b elx_fiq | Y |
first group | el1_serror_sp0 | b el1_serror_sp0 Jumping to yourself is equivalent to a dead cycle | N |
Group 2 | el1_sync_sp1 | b el1_sync_sp1 Jumping to yourself is equivalent to a dead cycle | N |
Group 2 | el1_irq_sp1 | b el1_irq_sp1 Jumping to yourself is equivalent to a dead cycle | N |
Group 2 | el1_fiq_sp1 | b el1_fiq_sp1 Jumping to yourself is equivalent to a dead cycle | N |
Group 2 | el1_serror_sp1 | b el1_serror_sp1 Jumping to yourself is equivalent to a dead cycle | N |
Group 3 | el0_sync_a64 | b el0_sync_abort | Y |
Group 3 | el0_irq_a64 | b elx_irq | Y |
Group 3 | el0_fiq_a64 | b elx_fiq | Y |
Group 3 | el0_serror_a64 | b el0_serror_a64 Jumping to yourself is equivalent to a dead cycle | N |
Group 4 | el0_sync_a32 | b el0_svc | Y |
Group 4 | el0_irq_a32 | b elx_irq | Y |
Group 4 | el0_fiq_a32 | b elx_fiq | Y |
Group 4 | el0_serror_a32 | b el0_serror_a32 Jumping to yourself is equivalent to a dead cycle | N |
To sum up, it is also easy to understand:
- In optee os, use sp_el0 stack supports user programs of aarch32 and aarch64, so the first, third and fourth groups of exception vectors are implemented. In addition, optee does not handle serror exceptions, so serror is not implemented.
- In the Linux kernel, use sp_el1 stack supports user programs of aarch32 and aarch64, so the second, third and fourth groups of exception vectors are realized
(Note: Although the Linux Kernel implements the FIQ vector, the logic under the vector finally jumps to the panic() function, that is, if the FIQ from target to Linux Kernel is triggered, panic will occur.)
(4),elx_irq and elx_fiq
Taking irq/fiq as an example, we can also find that no matter what kind of grouping exception, the final jump is the same kind of function: elx_irq and elx_fiq, that is, in either of the following cases, the jump is elx_irq and elx_fiq function.
- An irq/fiq interrupt occurred when PE was executing at the optee os privilege level (S-EL1)
- When the PE executes aarch64 at the userspace non privileged level (S-EL0), an irq/fiq interrupt comes
- When PE executes aarch32 in userspace non privileged level (S-user mode), an irq/fiq interrupt comes
4. Definition of base address of optee exception vector table
From the implementation of the exception vector table above, it can be found that the exception vector is defined in thread_ excp_ In the vect function, how is the function (exception vector) laid out in memory? How is the base address of the function written to VBAR_EL1?
FUNC thread_excp_vect , : align=2048
thread_init_vbar(vaddr_t addr) writes addr to vbar_el1
(optee_os/core/arch/arm/kernel/thread_a64.S) FUNC thread_init_vbar , : msr vbar_el1, x0 ret END_FUNC thread_init_vbar
get_excp_vect() returns the base address of the exception vector table (virtual address of course)
(optee_os/core/arch/arm/kernel/thread.c) static vaddr_t get_excp_vect(void) { #ifdef CFG_CORE_WORKAROUND_SPECTRE_BP_SEC uint32_t midr = read_midr(); if (get_midr_implementer(midr) != MIDR_IMPLEMENTER_ARM) return (vaddr_t)thread_excp_vect; switch (get_midr_primary_part(midr)) { #ifdef ARM32 case CORTEX_A8_PART_NUM: case CORTEX_A9_PART_NUM: case CORTEX_A17_PART_NUM: #endif case CORTEX_A57_PART_NUM: case CORTEX_A72_PART_NUM: case CORTEX_A73_PART_NUM: case CORTEX_A75_PART_NUM: return select_vector((vaddr_t)thread_excp_vect_workaround); #ifdef ARM32 case CORTEX_A15_PART_NUM: return select_vector((vaddr_t)thread_excp_vect_workaround_a15); #endif default: return (vaddr_t)thread_excp_vect; } #endif /*CFG_CORE_WORKAROUND_SPECTRE_BP_SEC*/ return (vaddr_t)thread_excp_vect; }
About starting from cpu (setting VBAR_EL1 when starting from cpu):
- If ATF is implemented in the whole system, CFG_ WITH_ ARM_ TRUSTED_ If the FW macro is open, the slave CPU is booted_ cpu_ on_ The handler is started, that is, it is transferred from the ATF.
- If ATF is not implemented in the whole system, CFG_ WITH_ ARM_ TRUSTED_ If the FW macro is closed, the slave cpu is reset_ secondary---->boot_ init_ I called it
(optee_os/core/arch/arm/kernel/boot.c) #if defined(CFG_WITH_ARM_TRUSTED_FW) unsigned long boot_cpu_on_handler(unsigned long a0 __maybe_unused, unsigned long a1 __unused) { init_secondary_helper(PADDR_INVALID); return 0; } #else void boot_init_secondary(unsigned long nsec_entry) { init_secondary_helper(nsec_entry); } #endif
Careful students can find that:
- The armv8-aarch64 architecture implements ATF, generally CFG_WITH_ARM_TRUSTED_FW macros are also open
- In the aarch64 system of optee, boot is not called_ init_ Secondary function, only in optee_ os/core/arch/arm/kernel/entry_ Reset in A32. S_ Boot was called in secondary_ init_ secondary()
5,elx_irq and elx_fiq
gicv3/gicv2 are handled differently
- If it is gicv2, irq will be regarded as an external system interrupt and fiq as an internal system interrupt;
- If it is gicv3, on the contrary, fiq is regarded as an external system interrupt and irq is regarded as an internal system interrupt
(note from the perspective of optee interrupt software, gic can be divided into two categories: gicv2 and non gicv2. Gicv3 mentioned here is actually non gicv2. If you use gicv4, you will also define CFG_ARM_GICV3 macro)
Handling of this system interrupt and external system interrupt:
- If the system is interrupted, call native_intr_handler
- If it is an external system interrupt, call foreign_intr_handler
(optee_os/core/arch/arm/kernel/thread_a64.S) LOCAL_FUNC elx_irq , : #if defined(CFG_ARM_GICV3) native_intr_handler irq #else foreign_intr_handler irq #endif END_FUNC elx_irq LOCAL_FUNC elx_fiq , : #if defined(CFG_ARM_GICV3) foreign_intr_handler fiq #else native_intr_handler fiq #endif END_FUNC elx_fiq