Interpretation of exception vector table in optee3.14 -- Interpretation of interrupt processing

Posted by andrei.mita on Sat, 20 Nov 2021 18:52:55 +0100

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

groupAnomaly vectorProcessed functionsDetermine whether to implement
first groupel1_sync_sp0b el1_sync_abortY
first groupel1_irq_sp0b elx_irqY
first groupel1_fiq_sp0b elx_fiqY
first groupel1_serror_sp0b el1_serror_sp0
Jumping to yourself is equivalent to a dead cycle
N
Group 2el1_sync_sp1b el1_sync_sp1
Jumping to yourself is equivalent to a dead cycle
N
Group 2el1_irq_sp1b el1_irq_sp1
Jumping to yourself is equivalent to a dead cycle
N
Group 2el1_fiq_sp1b el1_fiq_sp1
Jumping to yourself is equivalent to a dead cycle
N
Group 2el1_serror_sp1b el1_serror_sp1
Jumping to yourself is equivalent to a dead cycle
N
Group 3el0_sync_a64b el0_sync_abortY
Group 3el0_irq_a64b elx_irqY
Group 3el0_fiq_a64b elx_fiqY
Group 3el0_serror_a64b el0_serror_a64
Jumping to yourself is equivalent to a dead cycle
N
Group 4el0_sync_a32b el0_svcY
Group 4el0_irq_a32b elx_irqY
Group 4el0_fiq_a32b elx_fiqY
Group 4el0_serror_a32b 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