Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Исключения

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