Files
yggdrasil/kernel/src/arch/x86_64/exception.rs
T

279 lines
7.4 KiB
Rust

//! x86-64 exception and interrupt handling
use core::{arch::global_asm, mem::size_of};
use abi::{bitflags, primitive_enum, process::Signal};
use kernel_arch_x86::registers::{CR2, CR3};
use kernel_arch_x86_64::context::ExceptionFrame;
use libk::{arch::Cpu, task::thread::Thread};
use libk_mm::PageFaultKind;
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
use tock_registers::interfaces::Readable;
use crate::arch::x86_64::{X86_64, apic};
primitive_enum! {
enum ExceptionKind: u64 {
DivisionError = 0,
Debug = 1,
NonMaskableInterrupt = 2,
Breakpoint = 3,
Overflow = 4,
BoundRangeExceeded = 5,
InvalidOpcode = 6,
DeviceNotAvailable = 7,
DoubleFault = 8,
InvalidTss = 10,
SegmentNotPresent = 11,
StackSegmentFault = 12,
GeneralProtectionFault = 13,
PageFault = 14,
FpuException = 16,
AlignmentCheck = 17,
MachineCheck = 18,
SimdFpuException = 19,
VirtualizationException = 20,
ControlProtectionException = 21,
Unknown = 99,
}
}
impl ExceptionKind {
fn ring3_possible(&self) -> bool {
matches!(
self,
Self::DivisionError
| Self::Debug
| Self::Breakpoint
| Self::Overflow
| Self::BoundRangeExceeded
| Self::InvalidOpcode
| Self::GeneralProtectionFault
| Self::PageFault
| Self::FpuException
| Self::AlignmentCheck
| Self::SimdFpuException
)
}
}
/// Exception table entry
#[allow(dead_code)]
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Entry {
base_lo: u16,
selector: u16,
__res0: u8,
flags: u8,
base_hi: u16,
base_ex: u32,
__res1: u32,
}
#[allow(dead_code)]
#[repr(C, packed)]
struct Pointer {
limit: u16,
offset: usize,
}
const SIZE: usize = 256;
impl Entry {
/// Entry is valid
pub const PRESENT: u8 = 1 << 7;
/// Entry is a 32-bit interrupt
pub const INT32: u8 = 0xE;
/// Empty entry
pub const NULL: Self = Self {
base_lo: 0,
base_hi: 0,
base_ex: 0,
selector: 0,
flags: 0,
__res0: 0,
__res1: 0,
};
/// Constructs an interrupt table entry
pub const fn new(base: usize, selector: u16, flags: u8) -> Self {
Self {
base_lo: (base & 0xFFFF) as u16,
base_hi: ((base >> 16) & 0xFFFF) as u16,
base_ex: (base >> 32) as u32,
selector,
flags,
__res0: 0,
__res1: 0,
}
}
}
static IDT: IrqSafeRwLock<[Entry; SIZE]> = IrqSafeRwLock::new([Entry::NULL; SIZE]);
bitflags! {
struct PageFaultFlags: usize {
/// If clear, fault was caused by a non-present page entry
const P: bit 0;
/// If set, fault was caused by a write
const W: bit 1;
}
}
fn dump_user_exception(kind: ExceptionKind, frame: &ExceptionFrame) {
let thread = Thread::current();
log::warn!("{:?} in {} ({:?})", kind, thread.id, *thread.name.read());
log::warn!("ip = {:02x}:{:08x}", frame.cs, frame.rip);
log::warn!("cr3 = {:#010x}", CR3.get());
if kind == ExceptionKind::PageFault {
log::warn!("cr2 = {:#010x}", CR2.get());
}
}
fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) {
let thread = Thread::current();
let dump = match kind {
ExceptionKind::Debug => {
if thread.handle_single_step(frame) {
false
} else {
thread.raise_signal(Signal::Aborted);
true
}
}
ExceptionKind::PageFault => {
let cr2 = CR2.get();
let flags = PageFaultFlags::new(frame.exc_code as usize);
let kind = match (
flags.contains(PageFaultFlags::P),
flags.contains(PageFaultFlags::W),
) {
(true, true) => PageFaultKind::WriteFault,
(true, false) => PageFaultKind::Other,
(false, write) => PageFaultKind::TranslationFault { write },
};
if thread.handle_page_fault(cr2, kind).is_ok() {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
}
ExceptionKind::GeneralProtectionFault => {
if thread.handle_breakpoint(frame) {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
}
ExceptionKind::FpuException => {
thread.raise_signal(Signal::Aborted);
true
}
ExceptionKind::InvalidOpcode => {
// TODO handle ud2 as breakpoint? (it's 2 bytes)
thread.raise_signal(Signal::Aborted);
true
}
ExceptionKind::Breakpoint => {
thread.raise_signal(Signal::Aborted);
true
}
ExceptionKind::SimdFpuException => {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
_ => {
log::warn!("No handler for exception: {:?}", kind);
thread.raise_signal(Signal::Aborted);
true
}
};
if dump {
dump_user_exception(kind, frame);
}
}
fn kernel_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) -> ! {
let cr3 = CR3.get();
log::error!("{:?} in KERNEL, frame {:p}, cr3 = {:#x}", kind, frame, cr3);
if kind == ExceptionKind::PageFault {
let cr2 = CR2.get();
log::error!("cr2 = {:#x}", cr2);
}
// XXX
// frame.dump(debug::LogLevel::Fatal);
panic!("Irrecoverable exception");
}
extern "C" fn __x86_64_exception_handler(frame: *mut ExceptionFrame) {
let frame = unsafe { &mut *frame };
let kind = ExceptionKind::try_from(frame.exc_number).unwrap_or(ExceptionKind::Unknown);
if kind.ring3_possible() && frame.cs == 0x23 {
user_exception_inner(kind, frame);
unsafe {
Thread::current().handle_pending_signals(frame);
}
} else {
kernel_exception_inner(kind, frame)
}
}
extern "C" fn __x86_64_nmi_handler() -> ! {
let ipi = Cpu::local().get_ipi();
if let Some(ipi) = ipi {
X86_64::handle_ipi(Some(ipi));
unreachable!();
} else {
panic!("Spurious/unknown NMI received");
}
}
/// Initializes the interrupt descriptor table for the given CPU.
///
/// # Safety
///
/// Only meant to be called once per each CPU during their init.
pub unsafe fn init_exceptions(cpu_index: usize) {
if cpu_index == 0 {
let mut idt = IDT.write();
unsafe extern "C" {
static __x86_64_exception_vectors: [usize; 32];
}
for (i, &entry) in unsafe { __x86_64_exception_vectors.iter().enumerate() } {
idt[i] = Entry::new(entry, 0x08, Entry::PRESENT | Entry::INT32);
}
apic::setup_vectors(&mut idt[32..]);
}
let idt = IDT.read();
let idtr = Pointer {
limit: (idt.len() * size_of::<Entry>()) as u16 - 1,
offset: idt.as_ptr().addr(),
};
unsafe {
core::arch::asm!("wbinvd; lidt ({0})", in(reg) &idtr, options(att_syntax));
}
}
global_asm!(
include_str!("vectors.S"),
exception_handler = sym __x86_64_exception_handler,
nmi_handler = sym __x86_64_nmi_handler,
options(att_syntax)
);