Исключения
AArch64 определяет таблицу векторов исключений с 16 записями для 4 типов исключений (синхронное, IRQ, FIQ, SError) из 4 состояний (текущий EL с SP0, текущий EL с SPx, более низкий EL в AArch64, более низкий EL в AArch32). Мы реализуем её на ассемблере, чтобы сохранять энергозависимые регистры в стеке перед вызовом кода Rust:
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
use log::error;
use smccc::Hvc;
use smccc::psci::system_off;
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_current(_elr: u64, _spsr: u64) {
error!("sync_current");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn irq_current(_elr: u64, _spsr: u64) {
error!("irq_current");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
error!("fiq_current");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn serror_current(_elr: u64, _spsr: u64) {
error!("serror_current");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
error!("sync_lower");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn irq_lower(_elr: u64, _spsr: u64) {
error!("irq_lower");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
error!("fiq_lower");
system_off::<Hvc>().unwrap();
}
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn serror_lower(_elr: u64, _spsr: u64) {
error!("serror_lower");
system_off::<Hvc>().unwrap();
}
- EL — уровень исключений; все наши примеры сегодня днём работают в EL1.
- Для простоты мы не различаем SP0 и SPx для исключений текущего EL, а также AArch32 и AArch64 для исключений более низкого EL.
- В этом примере мы просто логируем исключение и выключаем питание, потому что не ожидаем, что какое-либо из них действительно произойдёт.
- Обработчики исключений и наш основной контекст выполнения можно более или менее рассматривать как разные потоки.
SendиSyncбудут определять, что мы можем разделять между ними, как и для потоков. Например, если мы хотим разделить некоторое значение между обработчиками исключений и остальной программой, и оноSend, но неSync, нам понадобится обернуть его во что-то вродеMutexи поместить вstatic.
Ассемблерный код вектора исключений:
/**
* Saves the volatile registers onto the stack. This currently takes
* 14 instructions, so it can be used in exception handlers with 18
* instructions left.
*
* On return, x0 and x1 are initialised to elr_el2 and spsr_el2
* respectively, which can be used as the first and second arguments
* of a subsequent call.
*/
.macro save_volatile_to_stack
/* Reserve stack space and save registers x0-x18, x29 & x30. */
stp x0, x1, [sp, #-(8 * 24)]!
stp x2, x3, [sp, #8 * 2]
stp x4, x5, [sp, #8 * 4]
stp x6, x7, [sp, #8 * 6]
stp x8, x9, [sp, #8 * 8]
stp x10, x11, [sp, #8 * 10]
stp x12, x13, [sp, #8 * 12]
stp x14, x15, [sp, #8 * 14]
stp x16, x17, [sp, #8 * 16]
str x18, [sp, #8 * 18]
stp x29, x30, [sp, #8 * 20]
/*
* Save elr_el1 & spsr_el1. This such that we can take nested
* exception and still be able to unwind.
*/
mrs x0, elr_el1
mrs x1, spsr_el1
stp x0, x1, [sp, #8 * 22]
.endm
/**
* Restores the volatile registers from the stack. This currently
* takes 14 instructions, so it can be used in exception handlers
* while still leaving 18 instructions left; if paired with
* save_volatile_to_stack, there are 4 instructions to spare.
*/
.macro restore_volatile_from_stack
/* Restore registers x2-x18, x29 & x30. */
ldp x2, x3, [sp, #8 * 2]
ldp x4, x5, [sp, #8 * 4]
ldp x6, x7, [sp, #8 * 6]
ldp x8, x9, [sp, #8 * 8]
ldp x10, x11, [sp, #8 * 10]
ldp x12, x13, [sp, #8 * 12]
ldp x14, x15, [sp, #8 * 14]
ldp x16, x17, [sp, #8 * 16]
ldr x18, [sp, #8 * 18]
ldp x29, x30, [sp, #8 * 20]
/*
* Restore registers elr_el1 & spsr_el1, using x0 & x1 as scratch.
*/
ldp x0, x1, [sp, #8 * 22]
msr elr_el1, x0
msr spsr_el1, x1
/* Restore x0 & x1, and release stack space. */
ldp x0, x1, [sp], #8 * 24
.endm
/**
* This is a generic handler for exceptions taken at the current EL. It saves
* volatile registers to the stack, calls the Rust handler, restores volatile
* registers, then returns.
*
* This also works for exceptions taken from lower ELs, if we don't care about
* non-volatile registers.
*
* Saving state and jumping to the Rust handler takes 15 instructions, and
* restoring and returning also takes 15 instructions, so we can fit the whole
* handler in 30 instructions, under the limit of 32.
*/
.macro current_exception handler:req
save_volatile_to_stack
bl \handler
restore_volatile_from_stack
eret
.endm
.section .text.vector_table_el1, "ax"
.global vector_table_el1
.balign 0x800
vector_table_el1:
sync_cur_sp0:
current_exception sync_current
.balign 0x80
irq_cur_sp0:
current_exception irq_current
.balign 0x80
fiq_cur_sp0:
current_exception fiq_current
.balign 0x80
serr_cur_sp0:
current_exception serror_current
.balign 0x80
sync_cur_spx:
current_exception sync_current
.balign 0x80
irq_cur_spx:
current_exception irq_current
.balign 0x80
fiq_cur_spx:
current_exception fiq_current
.balign 0x80
serr_cur_spx:
current_exception serror_current
.balign 0x80
sync_lower_64:
current_exception sync_lower
.balign 0x80
irq_lower_64:
current_exception irq_lower
.balign 0x80
fiq_lower_64:
current_exception fiq_lower
.balign 0x80
serr_lower_64:
current_exception serror_lower
.balign 0x80
sync_lower_32:
current_exception sync_lower
.balign 0x80
irq_lower_32:
current_exception irq_lower
.balign 0x80
fiq_lower_32:
current_exception fiq_lower
.balign 0x80
serr_lower_32:
current_exception serror_lower