RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / arch / aarch64 / exceptions.rs
5415 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! Exceptions and interrupts.
//!
//! On arm64 everything that interrupts normal flow is an "exception": a
//! fault, a system call, a hardware interrupt (IRQ). The CPU jumps into a
//! **vector table** whose address is in `VBAR_EL1`: 16 slots of 128 bytes,
//! 4 kinds (synchronous, IRQ, FIQ, SError) for each of 4 situations
//! (current level with SP_EL0, current level with SP_ELx, lower level in
//! 64-bit, lower level in 32-bit). We always run at EL1 on SP_EL1, so slots
//! 4-7 are the ones that matter for now.
//!
//! Each slot saves x0/x1 and jumps to common code that saves the rest
//! (plus ELR_EL1, where to return, and SPSR_EL1, the saved flags) and calls
//! `aarch64_exception(kind, frame)`.
//!
//! - **IRQ**: ask the interrupt controller which one, handle it (the
//!   timer), say we're done, return with `eret`.
//! - **anything synchronous** (a bad memory access, an undefined
//!   instruction, `brk`...): print what happened, using `ESR_EL1` (the
//!   "syndrome": what kind of exception, `EC` = bits 31:26) and `ELR_EL1`
//!   (the instruction that caused it), and stop: under QEMU through
//!   semihosting with exit status 3, so a script can tell.

use super::{board, console, timer};
use core::arch::asm;

core::arch::global_asm!(
    r#"
.macro VENTRY kind
.balign 0x80
    sub     sp, sp, #272
    stp     x0, x1, [sp, #0]
    mov     x0, #\kind
    b       __exc_common
.endm

.section .text.vectors, "ax"
.balign 0x800
.global __vectors
__vectors:
    VENTRY 0
    VENTRY 1
    VENTRY 2
    VENTRY 3
    VENTRY 4
    VENTRY 5
    VENTRY 6
    VENTRY 7
    VENTRY 8
    VENTRY 9
    VENTRY 10
    VENTRY 11
    VENTRY 12
    VENTRY 13
    VENTRY 14
    VENTRY 15

__exc_common:
    stp     x2, x3, [sp, #16]
    stp     x4, x5, [sp, #32]
    stp     x6, x7, [sp, #48]
    stp     x8, x9, [sp, #64]
    stp     x10, x11, [sp, #80]
    stp     x12, x13, [sp, #96]
    stp     x14, x15, [sp, #112]
    stp     x16, x17, [sp, #128]
    stp     x18, x19, [sp, #144]
    stp     x20, x21, [sp, #160]
    stp     x22, x23, [sp, #176]
    stp     x24, x25, [sp, #192]
    stp     x26, x27, [sp, #208]
    stp     x28, x29, [sp, #224]
    mrs     x1, elr_el1
    stp     x30, x1, [sp, #240]
    mrs     x1, spsr_el1
    str     x1, [sp, #256]
    mov     x1, sp
    bl      aarch64_exception
    ldr     x1, [sp, #256]
    msr     spsr_el1, x1
    ldp     x30, x1, [sp, #240]
    msr     elr_el1, x1
    ldp     x2, x3, [sp, #16]
    ldp     x4, x5, [sp, #32]
    ldp     x6, x7, [sp, #48]
    ldp     x8, x9, [sp, #64]
    ldp     x10, x11, [sp, #80]
    ldp     x12, x13, [sp, #96]
    ldp     x14, x15, [sp, #112]
    ldp     x16, x17, [sp, #128]
    ldp     x18, x19, [sp, #144]
    ldp     x20, x21, [sp, #160]
    ldp     x22, x23, [sp, #176]
    ldp     x24, x25, [sp, #192]
    ldp     x26, x27, [sp, #208]
    ldp     x28, x29, [sp, #224]
    ldp     x0, x1, [sp, #0]
    add     sp, sp, #272
    eret
"#
);

/// What the common code saved, in this order.
#[repr(C)]
pub struct Frame {
    pub x: [u64; 31],
    pub elr: u64,
    pub spsr: u64,
}

unsafe extern "C" {
    static __vectors: u8;
}

/// Point VBAR_EL1 at the table.
pub fn init() {
    unsafe {
        asm!("msr vbar_el1, {}", "isb", in(reg) &raw const __vectors as u64);
    }
}

/// Let IRQs in (clear the I bit in DAIF).
pub fn enable() {
    unsafe { asm!("msr daifclr, #2", options(nomem, nostack)) };
}

const KINDS: [&str; 4] = ["synchronous", "IRQ", "FIQ", "SError"];
const FROM: [&str; 4] = ["EL1 with SP_EL0", "EL1", "EL0 (64-bit)", "EL0 (32-bit)"];

/// What `ESR_EL1.EC` means, for the ones we're likely to see.
pub fn describe_ec(ec: u64) -> &'static str {
    match ec {
        0x00 => "unknown reason (an undefined instruction?)",
        0x01 => "WFI/WFE trapped",
        0x07 => "FP/SIMD access while disabled",
        0x0e => "illegal execution state",
        0x15 => "SVC (system call) from 64-bit code",
        0x18 => "MSR/MRS trapped",
        0x20 => "instruction abort from a lower level",
        0x21 => "instruction abort (bad address to run code from)",
        0x22 => "PC not aligned",
        0x24 => "data abort from a lower level",
        0x25 => "data abort (bad memory access)",
        0x26 => "SP not aligned",
        0x2f => "SError",
        0x3c => "BRK instruction",
        _ => "(see the Arm ARM, ESR_EL1)",
    }
}

#[unsafe(no_mangle)]
extern "C" fn aarch64_exception(kind: u64, frame: &mut Frame) {
    if kind == 5 {
        handle_irq();
        return;
    }
    let (esr, far): (u64, u64);
    unsafe {
        asm!("mrs {}, esr_el1", "mrs {}, far_el1", out(reg) esr, out(reg) far);
        console::force_unlock();
    }
    let ec = esr >> 26;
    crate::println!(
        "[exception] {} exception from {}: ESR_EL1={:#010x} (EC {:#04x}: {})",
        KINDS[(kind & 3) as usize],
        FROM[(kind >> 2) as usize & 3],
        esr,
        ec,
        describe_ec(ec)
    );
    crate::println!("[exception] ELR_EL1={:#x} FAR_EL1={:#x} SPSR_EL1={:#x}", frame.elr, far, frame.spsr);
    crate::println!("[exception] halting (QEMU exit status 3)");
    super::exit(3);
}

fn handle_irq() {
    let b = board::current();
    while let Some(irq) = b.irq().ack() {
        if irq == b.timer_irq() {
            timer::on_interrupt();
        } else {
            crate::println!("[irq] unexpected interrupt {}", irq);
        }
        b.irq().eoi(irq);
    }
}