baron · 11月25日

optee 中的异常向量表解读--中断处理解读

文章目录

  • 1、armv8-aarch64 的异常向量表介绍
  • 2、armv8 的 VBAR_ELx 寄存器
  • 3、optee 异常向量表的实现
  • 4、optee 异常向量表基地址的定义
  • 5、elx_irq 和 elx_fiq

1、armv8-aarch64 的异常向量表介绍

image.png

我们可以看出,实际上有四组表,每组表有四个异常入口,分别对应同步异常,IRQ,FIQ 和 serror。

  • 如果发生异常后并没有 exception level 切换,并且发生异常之前使用的栈指针是 SP_EL0,那么使用第一组异常向量表。
  • 如果发生异常后并没有 exception level 切换,并且发生异常之前使用的栈指针是 SP_EL1/2/3,那么使用第二组异常向量表。
  • 如果发生异常导致了 exception level 切换,并且发生异常之前的 exception level 运行在 AARCH64 模式,那么使用第三组异常向量表。
  • 如果发生异常导致了 exception level 切换,并且发生异常之前的 exception level 运行在 AARCH32 模式,那么使用第四组异常向量表。

另外我们还可以看到的一点是,每一个异常入口不再仅仅占用 4bytes 的空间,而是占用 0x80 bytes 空间,也就是说,每一个异常入口可以放置多条指令,而不仅仅是一条跳转指令

2、armv8 的 VBAR_ELx 寄存器

armv8 定义了 VBAR_EL1、VBAR_EL2、VBAR_EL3 三个基地址寄存器

image.png

3、optee 异常向量表的实现

(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_size 其实就是检查异常向量中的指令 size,不能草果 32*4=128 字节,因为 armv8-arch64 定义的异常向量每一个 offset 中的地址范围是 128 字节

    .macro check_vector_size since
      .if (. - \since) > (32 * 4)
        .error "Vector exceeds 32 instructions"
      .endif
    .endm

(2)、128 字节对其的异常向量
balign 128 就是告诉汇编代码,接下来的函数定义是 128 字节对其的。这也和armv8-arch64定义的异常向量的地址范围一致

.balign    128, INV_INSN

(3)、异常向量实现的总结

image.png

  • 在 optee os 中,使用的 sp_el0 栈,同时支持 aarch32、aarch64 的 user 程序,所以实现了第一、三、四组异常向量,另外 optee 不处理 serror 异常,所以 serror 也不实现。
  • 在 Linux kernel 中,使用 sp_el1 栈,同时支持 aarch32、aarch64 的 user 程序,所以实现了第二、三、四组异常向量.
    (注:虽然 Linux Kernel 实现了 FIQ 向量,但该向量下的逻辑最终跳转到 panic()函数,也就是如果触发了 target 到 Linux Kernel 的 FIQ,将发生 panic.)

(4)、elx_irq 和 elx_fiq
以 irq/fiq 为例,我们还可以发现,无论是哪种分组异常,最终跳转的都是同一类函数:elx_irq 和 elx_fiq,即无论是下面哪种情况,跳转的都是 elx_irq 和 elx_fiq 函数。

  • PE 在 optee os 特权级(S-EL1)执行时,来了一个 irq/fiq 中断
  • PE 在 userspace 非特权级(S-EL0)执行 aarch64 时,来了一个 irq/fiq 中断
  • PE 在 userspace 非特权级(S-user mode)执行 aarch32 时,来了一个 irq/fiq 中断

4、optee 异常向量表基地址的定义

从上文的_异常向量表的实现_中可以发现,异常向量定义在了 thread_excp_vect 函数中, 那么该函数(异常向量)是如何布局到内存的? 该函数的基地址又是如何写入到 VBAR_EL1 的?

FUNC thread_excp_vect , : align=2048

image.png

thread_init_vbar(vaddr_t addr)将 addr 写入到 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()返回异常向量表基地址(当然是虚拟地址)

(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;
}

关于从 cpu 的启动(从 cpu 启动时设置 VBAR_EL1):

image.png

  • 如果在整个系统中有实现 ATF,则 CFG_WITH_ARM_TRUSTED_FW 宏是打开的,那么从 cpu 是从 boot_cpu_on_handler 启动,也就是从 ATF 调来的。
  • 如果在整个系统中没有实现 ATF,则 CFG_WITH_ARM_TRUSTED_FW 宏是关闭的,那么从 cpu 是从 reset_secondary---->boot_init_secondary 调用过来的
(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

细心的同学看代码可以发现:

  • armv8-aarch64 架构都是有实现 ATF,一般情况下 CFG_WITH_ARM_TRUSTED_FW 宏也都是打开的
  • 在 optee 的 aarch64 体系中,是没有调用 boot_init_secondary 函数的,仅仅在 optee_os/core/arch/arm/kernel/entry_a32.S 中的 reset_secondary 中进行了调用 boot_init_secondary()

5、elx_irq 和 elx_fiq

gicv3/gicv2 有着不同的处理

  • 如果是 gicv2,则会将 irq 视为外系统中断,fiq 视为本系统中断
  • 如果是 gicv3,恰好相反,将 fiq 视为外系统中断,irq 视为本系统中断.

(注从 optee 中断软件的视角来看,gic 可以分为两类,gicv2、非 gicv2, 这里说说的 gicv3 其实就是非 gicv2,如果你使用的是 gicv4,那么也会定义 CFG_ARM_GICV3 宏)

本系统中断和外部系统中断的处理:

  • 如果是本系统中断,则调用 native_intr_handler
  • 如果是外部系统中断则调用 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

image.png

推荐阅读
关注数
9466
内容数
212
以易懂、渐进、有序的方式,深入探讨ARMv8/ARMv9架构的核心概念。我们将从基础知识开始,逐步深入,覆盖最新的架构,不再纠缠于过时技术。本系列内容包含但不限于ARM基础、SOC芯片基础、Trustzone、gic、异常和中断、AMBA、Cache、MMU等内容,并将持续更新。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息