据上篇 Linaro RMM 代码初步阅读,我们已经了解到,当 Realm 发生异常时,会直接回退到 RMM 的上下文。RMM 将 Realm 的上下文保存到 REC 结构体中,调用
handle_realm_exit()
函数对 Realm 的异常进行处理。
1 Realm 的异常判断和分发
handle_realm_exit()
函数有三个参数,分别是 REC 结构体地址,RecExit 结构体地址和异常值。对于异常值,是从 Realm 退出后,由 el2_vectors
代码设置的。
el2_vectors
代码在rmm/runtime/core/aarch64/vectors.S
中实现,设置了所有中断处理函数的入口地址,这些地址应该在初始化的时候就被设置到了中断向量表中。当中断发生时,直接进入相应函数。ENTRY(el2_vectors): ventry_unused exc_sync_sp0 ventry_unused exc_irq_sp0 ventry_unused exc_fiq_sp0 ventry_unused exc_serror_sp0 ventry el2_sync_cel ventry_unused exc_irq_spx ventry_unused exc_fiq_spx ventry_unused exc_serror_spx ventry el2_sync_lel ventry el2_irq_lel ventry el2_fiq_lel ventry el2_serror_lel ventry_unused exc_sync_lel_32 ventry_unused exc_irq_lel_32 ventry_unused exc_fiq_lel_32 ventry_unused exc_serror_lel_32 ENDPROC(el2_vectors)
X0 寄存器是 aarch64 架构函数返回值所用的寄存器。
每个中断处理函数将异常信息码存储到 X0 寄存器中,并跳转
realm_exit()
函数。realm_exit()
函数在rmm/runtime/core/aarch64/run-asm.S
中实现。该函数保存 Realm 通用寄存器和恢复一些寄存器值后直接返回到rec_run_loop()
函数中。
handle_realm_exit()
函数在 rmm/runtime/core/exit.c
中实现。RMM 将 Realm 的异常分成了以下几类:
-
ARM_EXCEPTION_SYNC_LEL: 将 RecExit 退出原因设置成 RMI_EXIT_SYNC 后,交给
handle_exception_sync()
函数处理,由该函数决定是否需要 REC_EXIT 的操作。如果需要 REC_EXIT,则将一些 EL2 特权寄存器的值,如 ESR, FAR, HPFAR,保存在 REC 结构体的 last_run_info 域中。 -
ARM_EXCEPTION_IRQ_LEL: 交给
handle_exception_irq_lel()
函数处理,由该函数决定是否需要 REC_EXIT 的操作。 -
ARM_EXCEPTION_FIQ_LEL: 将 RecExit 退出原因设置成 RMI_EXIT_FIQ,并退出 switch 选择语句。
-
ARM_EXCEPTION_SERROR_LEL: 将 RecExit 退出原因设置成 RMI_EXIT_SERROR,然后交给
hanle_exception_serror_lel()
函数处理,并由该函数决定是否需要 REC_EXIT 操作。 -
其他情况,默认需要 REC_EXIT 操作。
2 同步异常的处理
handle_exception_sync()
函数在 rmm/runtime/core/exit.c
中实现。函数先读取 EL2 的 ESR 寄存器,并根据读取的值,判断是那种异常。函数能处理的异常有以下几类:
-
ESR_EL2_EC_WFX: WF* 类指令,设置 RecExit 的 ESR 值为读取的 EL2 的 ESR 值,PC 更新到下一条指令后,需要 REC_EXIT。
-
ESR_EL2_EC_HVC: Realm 执行了 HVC 指令,调用
realm_inject_undef_abort()
函数,注入异常并返回到 Realm 中。 -
ESR_EL2_EC_SMC: Realm 有 RSI 请求,调用
handle_realm_rsi()
函数进行处理,并由该函数决定是否需要 REC_EXIT。 -
ESR_EL2_EC_SYSREG: Realm 在尝试读取寄存器,调用
handle_sysreg_access_trap()
处理,并由该函数决定是否需要 REC_EXIT。返回时需要将 PC 值设置为下一条指令的 PC 的地址。 -
ESR_EL2_EC_INST_ABORT / ESR_EL2_EC_DATA_ABORT: 指令和数据的访问异常,分别调用
handle_inst_abort()
和handle_data_abort()
函数进行处理,并由这两个函数决定是否需要 REC_EXIT。 -
ESR_EL2_EC_FPU / ESR_EL2_EC_SVE / ESR_EL2_EC_SME: SIMD 相关异常,交给
handle_simd_exception()
函数处理并决定是否需要 REC_EXIT。该异常我们应该暂时不需要了解。 -
其他异常:未被定义的异常,需要 REC_EXIT。
2.1 异常注入
在 rmm/runtime/core/inject-exp.c
中,实现了几个异常注入的函数:
-
inject_sync_idabort()
-
inject_sync_idabort_rec()
-
realm_inject_undef_abort()
前两个主要区别在于,是否指定了注入异常的对象 REC。我们这里更关注最后一个,因为在 中提到了它。但这三者又非常的相似,涉及的主要操作如下:
-
读取 EL2 的 ESR / ELR 寄存器值和读取 EL12 的 SPSR / VBAR 寄存器的值。
-
根据 ESR 和 SPSR 的值,从 VBAR 中得到相应异常处理函数的地址,这里定义为 。
-
计算 PSTATE 的值。
-
将 ELR / ESR / SPSR 的值写入到 EL12 相应的寄存器中。
-
将 写入到 EL2 的 ELR 寄存器中。
-
将 PSTATE 值写入到 EL2 的 SPSR 寄存器中。
2.2 RSI 相关请求处理
handle_realm_rsi()
函数在 rmm/runtime/core/exit.c
中实现。函数先从 REC 中的 X0 寄存器读取 RSI 功能编号,并根据该功能编号执行相应 RSI 的服务函数。
2.3 寄存器读写处理
handle_sysreg_access_trap()
函数在 rmm/runtime/core/sysregs.c
中实现。函数先从 EL2 的 ESR 寄存器中读去要访问的 sysreg 的编号 和掩码 。根据 跳转相应的处理函数进行处理(主要是访问虚拟中断相关寄存器,如 ICC),并决定是否需要 REC_EXIT。其他情况,视为 RAZ 和 WI,返回到 Realm 中。
2.4 指令和数据异常
handle_inst_abort()
和 handle_data_abort
函数均在 rmm/runtime/core/exit.c
中实现。两者都先通过 handle_sync_external_abort()
函数判断是不是 SEA。如果是,就直接 REC_EXIT。
对于指令异常,则判断是否存在内存越界、访问未保护的 IPA 亦或者是 RIPAS 为 EMPTY 的情况。如果是,则通过 inject_sync_idabort()
函数注入异常,交给 Realm 进行处理。
对于数据异常,则判断是否存在内存越界或者是 RIPAS 为 EMPTY 的情况。如果是,则通过 inject_sync_idabort()
函数注入异常,交给 Realm 进行处理。
对于其他情况,视为未定义的异常。将相关的访问信息写入到 RecExit 结构体中,通过 REC_EXIT,交给普通环境中的 Host 处理。
3 IRQ 和 FIQ 的处理
虽然 IRQ 提供了 handle_exception_irq_lel()
函数,但和 FIQ 没有任何区别。分别把 RecExit 中的退出原因设置成 RMI_EXIT_IRQ 和 RMI_EXIT_FIQ 后 REC_EXIT,由普通环境中的 Host 注入虚拟中断。至于虚拟中断如何反馈给 Realm,在上一篇文章中有介绍。
4 SERROR 的处理
handle_exception_serror_lel()
函数在 rmm/runtime/core/exit.c
中实现。函数先读取 EL2 的 ESR 寄存器,判断 SERROR 的类型:
-
如果是未实现或者是未定义的 SERROR,直接让系统崩溃,停止运行。
-
如果是 ESR_EL2_SERROR_AET_UEU (Unrecoverable RAS Error) 和 ESR_EL2_SERROR_UER (Recoverable RAS Error),则调用
inject_serror()
函数向 Realm 注入异常,这样可以让 Realm 中运行的系统关机。将 ESR 寄存器的值写入到 RecExit 中,告诉普通环境中的 Host 有关该异常的信息。 -
如果是 ESR_EL2_SERROR_AET_CE (Corrected RAS Error) 和 ESR_EL2_SERROR_UEO (Restartable RAS Error),则将 ESR 寄存器的值到 RecExit 中,告诉普通环境中的 Host 有关该异常的信息。
-
以上两者都需要 REC_EXIT。对于其他 SERROR,直接让系统崩溃。
inject_serror()
函数在 rmm/runtime/core/run.c
中实现,将 ESR 信息设置到 REC 的 serror_info 域中。
以上就是 Linaro RMM 的异常处理部分。源代码中也有很多的 TODO,相当一部分的功能还没有实现。