547 lines
16 KiB
Rust
547 lines
16 KiB
Rust
use core::{arch::naked_asm, fmt::Debug, marker::PhantomData, ops::Deref, ptr::NonNull};
|
|
|
|
use bit_field::BitField;
|
|
|
|
use crate::{
|
|
serial_println,
|
|
x86_64::{RFlags, Registers, halt_loop},
|
|
};
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(C)]
|
|
pub struct Entry {
|
|
pub offset_low: u16,
|
|
pub selector: u16,
|
|
pub options: EntryOptions,
|
|
pub offset_middle: u16,
|
|
pub offset_high: u32,
|
|
pub reserved: u32,
|
|
}
|
|
|
|
impl core::fmt::Debug for Entry {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("Entry")
|
|
.field("handler", &self.handler_address())
|
|
.field("selector", &self.selector)
|
|
.field("options", &self.options)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
pub struct ExceptionVector;
|
|
impl ExceptionVector {
|
|
pub const DIVIDE_BY_ZERO: u8 = 0;
|
|
pub const DEBUG: u8 = 1;
|
|
pub const NON_MASKABLE_INTERRUPT: u8 = 2;
|
|
pub const BREAKPOINT: u8 = 3;
|
|
pub const OVERFLOW: u8 = 4;
|
|
pub const BOUND_RANGE_EXCEEDED: u8 = 5;
|
|
pub const INVALID_OPCODE: u8 = 6;
|
|
pub const DEVICE_NOT_AVAILABLE: u8 = 7;
|
|
pub const DOUBLE_FAULT: u8 = 8;
|
|
pub const COPROCESSOR_SEGMENT_OVERRUN: u8 = 9;
|
|
pub const INVALID_TSS: u8 = 10;
|
|
pub const SEGMENT_NOT_PRESENT: u8 = 11;
|
|
pub const STACK_SEGMENT_FAULT: u8 = 12;
|
|
pub const GENERAL_PROTECTION_FAULT: u8 = 13;
|
|
pub const PAGE_FAULT: u8 = 14;
|
|
|
|
pub const X87_FLOATING_POINT_EXCEPTION: u8 = 16;
|
|
pub const ALIGNMENT_CHECK: u8 = 17;
|
|
pub const MACHINE_CHECK: u8 = 18;
|
|
pub const SIMD_FLOATING_POINT_EXCEPTION: u8 = 19;
|
|
pub const VIRTUALIZATION_EXCEPTION: u8 = 20;
|
|
pub const CONTROL_PROTECTION_EXCEPTION: u8 = 21;
|
|
|
|
pub const HYPERVISOR_EXCEPTION: u8 = 28;
|
|
pub const VMM_COMMUNICATION_EXCEPTION: u8 = 29;
|
|
pub const SECURITY_EXCEPTION: u8 = 30;
|
|
}
|
|
|
|
impl Entry {
|
|
pub unsafe fn new<T: ToAddress>(handler: T, selector: u16, options: EntryOptions) -> Self {
|
|
let handler_addr = handler.to_address();
|
|
Self {
|
|
offset_low: handler_addr as u16,
|
|
selector,
|
|
options,
|
|
offset_middle: (handler_addr >> 16) as u16,
|
|
offset_high: (handler_addr >> 32) as u32,
|
|
reserved: 0,
|
|
}
|
|
}
|
|
|
|
pub unsafe fn new_default_interrupt<const IDX: u8>() -> Self {
|
|
let cs = unsafe { super::instructions::get_cs() };
|
|
let mut options = EntryOptions::empty_interrupt_gate();
|
|
options.set_present(true);
|
|
let handler = match IDX {
|
|
8 | 10 | 11 | 12 | 13 | 14 | 17 | 21 | 29 | 30 => {
|
|
interrupt_trampoline_with_err::<IDX> as *const () as u64
|
|
}
|
|
_ => interrupt_trampoline_no_err::<IDX> as *const () as u64,
|
|
};
|
|
|
|
match IDX {
|
|
ExceptionVector::DOUBLE_FAULT => {
|
|
options.set_interrupt_stack_table_index(super::gdt::DF_STACK);
|
|
}
|
|
ExceptionVector::NON_MASKABLE_INTERRUPT => {
|
|
options.set_interrupt_stack_table_index(super::gdt::NMI_STACK);
|
|
}
|
|
ExceptionVector::MACHINE_CHECK => {
|
|
options.set_interrupt_stack_table_index(super::gdt::MC_STACK);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
unsafe { Self::new(handler, cs, options) }
|
|
}
|
|
|
|
pub const fn missing_interrupt() -> Self {
|
|
Self {
|
|
offset_low: 0,
|
|
selector: 0,
|
|
options: EntryOptions::empty_interrupt_gate(),
|
|
offset_middle: 0,
|
|
offset_high: 0,
|
|
reserved: 0,
|
|
}
|
|
}
|
|
|
|
pub fn handler_address(&self) -> u64 {
|
|
let low = self.offset_low as u64;
|
|
let middle = self.offset_middle as u64;
|
|
let high = self.offset_high as u64;
|
|
(high << 32) | (middle << 16) | low
|
|
}
|
|
|
|
pub fn set_handler<T>(&mut self, handler: T)
|
|
where
|
|
T: ToAddress,
|
|
{
|
|
let handler_addr = handler.to_address();
|
|
self.offset_low = handler_addr as u16;
|
|
self.offset_middle = (handler_addr >> 16) as u16;
|
|
self.offset_high = (handler_addr >> 32) as u32;
|
|
|
|
self.options = EntryOptions::empty_interrupt_gate();
|
|
unsafe {
|
|
self.selector = super::instructions::get_cs();
|
|
}
|
|
self.options.set_present(true);
|
|
}
|
|
}
|
|
|
|
pub trait ToAddress {
|
|
fn to_address(&self) -> u64;
|
|
}
|
|
|
|
impl ToAddress for u64 {
|
|
fn to_address(&self) -> u64 {
|
|
*self
|
|
}
|
|
}
|
|
|
|
impl<T> ToAddress for *const T {
|
|
fn to_address(&self) -> u64 {
|
|
*self as u64
|
|
}
|
|
}
|
|
|
|
impl<T> ToAddress for *mut T {
|
|
fn to_address(&self) -> u64 {
|
|
*self as u64
|
|
}
|
|
}
|
|
|
|
impl<T> ToAddress for NonNull<T> {
|
|
fn to_address(&self) -> u64 {
|
|
self.as_ptr() as u64
|
|
}
|
|
}
|
|
|
|
impl<T> ToAddress for &T {
|
|
fn to_address(&self) -> u64 {
|
|
*self as *const T as u64
|
|
}
|
|
}
|
|
|
|
impl<T> ToAddress for &mut T {
|
|
fn to_address(&self) -> u64 {
|
|
*self as *const T as u64
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(transparent)]
|
|
pub struct EntryOptions(u16);
|
|
|
|
impl Debug for EntryOptions {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("EntryOptions")
|
|
.field("present", &self.present())
|
|
.field("privilege_level", &self.privilege_level())
|
|
.field(
|
|
"interrupt_stack_table_index",
|
|
&self.interrupt_stack_table_index(),
|
|
)
|
|
.field("kind", &self.kind())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
pub enum EntryType {
|
|
#[default]
|
|
InterruptGate = 0b1110,
|
|
TrapGate = 0b1111,
|
|
}
|
|
|
|
impl EntryType {
|
|
pub fn into_u8(self) -> u8 {
|
|
self as u8
|
|
}
|
|
|
|
/// # Safety
|
|
/// The caller must ensure that `value` is either `0b1110` or `0b1111`, or
|
|
/// the result of calling `into_u8` on an `EntryType`.
|
|
pub unsafe fn from_u8_unchecked(value: u8) -> Self {
|
|
unsafe { core::mem::transmute(value) }
|
|
}
|
|
}
|
|
|
|
impl EntryOptions {
|
|
pub const fn empty_interrupt_gate() -> Self {
|
|
Self(0b1110_0000_0000)
|
|
}
|
|
pub const fn empty_trap_gate() -> Self {
|
|
Self(0b1111_0000_0000)
|
|
}
|
|
|
|
pub fn with_present(mut self, present: bool) -> Self {
|
|
self.set_present(present);
|
|
self
|
|
}
|
|
pub fn with_privilege_level(mut self, level: u8) -> Self {
|
|
self.set_privilege_level(level);
|
|
self
|
|
}
|
|
pub fn with_interrupt_stack_table_index(mut self, index: u8) -> Self {
|
|
self.set_interrupt_stack_table_index(index);
|
|
self
|
|
}
|
|
pub fn with_kind(mut self, kind: EntryType) -> Self {
|
|
self.set_kind(kind);
|
|
self
|
|
}
|
|
|
|
pub fn present(&self) -> bool {
|
|
self.0.get_bit(15)
|
|
}
|
|
pub fn set_present(&mut self, present: bool) {
|
|
self.0.set_bit(15, present);
|
|
}
|
|
|
|
pub fn privilege_level(&self) -> u8 {
|
|
self.0.get_bits(13..15) as u8
|
|
}
|
|
pub fn set_privilege_level(&mut self, level: u8) {
|
|
self.0.set_bits(13..15, level as u16);
|
|
}
|
|
pub fn interrupt_stack_table_index(&self) -> Option<u8> {
|
|
let index = self.0.get_bits(0..3) as u8;
|
|
if index == 0 { None } else { Some(index - 1) }
|
|
}
|
|
pub fn set_interrupt_stack_table_index(&mut self, index: u8) {
|
|
self.0.set_bits(0..3, index as u16 + 1);
|
|
}
|
|
pub fn kind(&self) -> EntryType {
|
|
unsafe { EntryType::from_u8_unchecked(self.0.get_bits(8..12) as u8) }
|
|
}
|
|
pub fn set_kind(&mut self, kind: EntryType) {
|
|
self.0.set_bits(8..12, kind.into_u8() as u16);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(C)]
|
|
pub struct InterruptStackFrameInner {
|
|
pub instruction_pointer: u64,
|
|
pub code_segment: u16,
|
|
_reserved: [u16; 3],
|
|
pub flags: RFlags,
|
|
pub stack_pointer: u64,
|
|
pub stack_segment: u16,
|
|
_reserved2: [u16; 3],
|
|
}
|
|
|
|
#[repr(transparent)]
|
|
pub struct InterruptStackFrame(InterruptStackFrameInner);
|
|
|
|
impl Debug for InterruptStackFrame {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("InterruptStackFrame")
|
|
.field(
|
|
"instruction_pointer",
|
|
&format_args!("{:#x}", self.instruction_pointer),
|
|
)
|
|
.field("code_segment", &format_args!("{:#x}", self.code_segment))
|
|
.field("flags", &self.flags)
|
|
.field("stack_pointer", &format_args!("{:#x}", self.stack_pointer))
|
|
.field("stack_segment", &format_args!("{:#x}", self.stack_segment))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Deref for InterruptStackFrame {
|
|
type Target = InterruptStackFrameInner;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(transparent)]
|
|
pub struct PageFaultErrorCode: u64 {
|
|
const PRESENT = 1 << 0;
|
|
const WRITE = 1 << 1;
|
|
const USER = 1 << 2;
|
|
const RESERVED = 1 << 3;
|
|
const INSTRUCTION_FETCH = 1 << 4;
|
|
const PROTECTION_KEY = 1 << 5;
|
|
const SGX = 1 << 15;
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C, packed(2))]
|
|
pub struct IdtRegister<'idt> {
|
|
pub limit: u16,
|
|
pub base: u64,
|
|
_pd: PhantomData<&'idt ()>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C)]
|
|
pub struct InterruptDescriptorTable {
|
|
pub divide_by_zero: Entry,
|
|
pub debug: Entry,
|
|
pub non_maskable_interrupt: Entry,
|
|
pub breakpoint: Entry,
|
|
pub overflow: Entry,
|
|
pub bound_range_exceeded: Entry,
|
|
pub invalid_opcode: Entry,
|
|
pub device_not_available: Entry,
|
|
pub double_fault: Entry,
|
|
pub coprocessor_segment_overrun: Entry,
|
|
pub invalid_tss: Entry,
|
|
pub segment_not_present: Entry,
|
|
pub stack_segment_fault: Entry,
|
|
pub general_protection_fault: Entry,
|
|
pub page_fault: Entry,
|
|
_reserved1: Entry,
|
|
pub x87_floating_point: Entry,
|
|
pub alignment_check: Entry,
|
|
pub machine_check: Entry,
|
|
pub simd_floating_point: Entry,
|
|
pub virtualization: Entry,
|
|
pub cp_protection: Entry,
|
|
_reserved2: [Entry; 6],
|
|
pub hypervisor: Entry,
|
|
pub vmm_communication_exception: Entry,
|
|
pub security_exception: Entry,
|
|
_reserved3: Entry,
|
|
pub interrupts: [Entry; 256 - 32],
|
|
}
|
|
|
|
const impl Default for InterruptDescriptorTable {
|
|
fn default() -> Self {
|
|
Self::new_empty()
|
|
}
|
|
}
|
|
|
|
impl InterruptDescriptorTable {
|
|
pub const fn new_empty() -> Self {
|
|
Self {
|
|
divide_by_zero: Entry::missing_interrupt(),
|
|
debug: Entry::missing_interrupt(),
|
|
non_maskable_interrupt: Entry::missing_interrupt(),
|
|
breakpoint: Entry::missing_interrupt(),
|
|
overflow: Entry::missing_interrupt(),
|
|
bound_range_exceeded: Entry::missing_interrupt(),
|
|
invalid_opcode: Entry::missing_interrupt(),
|
|
device_not_available: Entry::missing_interrupt(),
|
|
double_fault: Entry::missing_interrupt(),
|
|
coprocessor_segment_overrun: Entry::missing_interrupt(),
|
|
invalid_tss: Entry::missing_interrupt(),
|
|
segment_not_present: Entry::missing_interrupt(),
|
|
stack_segment_fault: Entry::missing_interrupt(),
|
|
general_protection_fault: Entry::missing_interrupt(),
|
|
page_fault: Entry::missing_interrupt(),
|
|
_reserved1: Entry::missing_interrupt(),
|
|
x87_floating_point: Entry::missing_interrupt(),
|
|
alignment_check: Entry::missing_interrupt(),
|
|
machine_check: Entry::missing_interrupt(),
|
|
simd_floating_point: Entry::missing_interrupt(),
|
|
virtualization: Entry::missing_interrupt(),
|
|
cp_protection: Entry::missing_interrupt(),
|
|
_reserved2: [Entry::missing_interrupt(); 6],
|
|
hypervisor: Entry::missing_interrupt(),
|
|
vmm_communication_exception: Entry::missing_interrupt(),
|
|
security_exception: Entry::missing_interrupt(),
|
|
_reserved3: Entry::missing_interrupt(),
|
|
interrupts: [Entry::missing_interrupt(); 256 - 32],
|
|
}
|
|
}
|
|
|
|
pub fn new_default() -> Self {
|
|
let mut idt = Self::new_empty();
|
|
let slice = unsafe { idt.as_mut_type_erased() };
|
|
seq_macro::seq!(
|
|
N in 0..=255 {
|
|
slice[N] = unsafe { Entry::new_default_interrupt::<N>() };
|
|
}
|
|
);
|
|
idt
|
|
}
|
|
|
|
pub unsafe fn register(&self) -> IdtRegister<'_> {
|
|
IdtRegister {
|
|
limit: (core::mem::size_of::<Self>() - 1) as u16,
|
|
base: self as *const _ as u64,
|
|
_pd: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn load(&'static self) {
|
|
unsafe { Self::load_unsafe(self) };
|
|
}
|
|
|
|
pub unsafe fn load_unsafe(&self) {
|
|
unsafe {
|
|
super::instructions::lidt(&self.register());
|
|
}
|
|
}
|
|
|
|
pub unsafe fn as_type_erased(&self) -> &[Entry; 256] {
|
|
unsafe { core::mem::transmute::<&Self, &[Entry; 256]>(self) }
|
|
}
|
|
pub unsafe fn as_mut_type_erased(&mut self) -> &mut [Entry; 256] {
|
|
unsafe { core::mem::transmute::<&mut Self, &mut [Entry; 256]>(self) }
|
|
}
|
|
}
|
|
|
|
#[unsafe(naked)]
|
|
extern "C" fn interrupt_trampoline_with_err<const IDX: u8>() {
|
|
naked_asm!(
|
|
"push {idx}",
|
|
"jmp {dispatcher}",
|
|
idx = const { IDX },
|
|
dispatcher = sym interrupt_dispatcher,
|
|
)
|
|
}
|
|
|
|
#[unsafe(naked)]
|
|
extern "C" fn interrupt_trampoline_no_err<const IDX: u8>() {
|
|
naked_asm!(
|
|
"push 0", // push a dummy error code of 0
|
|
"push {idx}",
|
|
"jmp {dispatcher}",
|
|
idx = const { IDX },
|
|
dispatcher = sym interrupt_dispatcher,
|
|
)
|
|
}
|
|
|
|
#[unsafe(naked)]
|
|
extern "C" fn interrupt_dispatcher() {
|
|
naked_asm!(
|
|
"push rax",
|
|
"push rcx",
|
|
"push rdx",
|
|
"push rbx",
|
|
"push rbp",
|
|
"push rsi",
|
|
"push rdi",
|
|
"push r8",
|
|
"push r9",
|
|
"push r10",
|
|
"push r11",
|
|
"push r12",
|
|
"push r13",
|
|
"push r14",
|
|
"push r15",
|
|
"lea rdi, [rsp + {registers_size} + 16]", // interrupt frame
|
|
"mov rdx, [rsp + {registers_size} + 8]", // error code
|
|
"mov rsi, [rsp + {registers_size}]", // interrupt index
|
|
"mov rcx, rsp", // pass the current stack pointer as the registers pointer
|
|
"call {global_handler}",
|
|
"pop r15",
|
|
"pop r14",
|
|
"pop r13",
|
|
"pop r12",
|
|
"pop r11",
|
|
"pop r10",
|
|
"pop r9",
|
|
"pop r8",
|
|
"pop rdi",
|
|
"pop rsi",
|
|
"pop rbp",
|
|
"pop rbx",
|
|
"pop rdx",
|
|
"pop rcx",
|
|
"pop rax",
|
|
"add rsp, 16", // pop the interrupt index and error code
|
|
"iretq",
|
|
registers_size = const { core::mem::size_of::<Registers>() },
|
|
global_handler = sym global_interrupt_handler,
|
|
)
|
|
}
|
|
|
|
extern "C" fn global_interrupt_handler(
|
|
frame: &InterruptStackFrame,
|
|
index: u8,
|
|
error_code: u64,
|
|
registers: &Registers,
|
|
) {
|
|
serial_println!(
|
|
"Interrupt {} occurred! Error code: {:#x}, Frame: {:#?}, Registers: {:#?}",
|
|
index,
|
|
error_code,
|
|
frame,
|
|
registers
|
|
);
|
|
|
|
match index {
|
|
ExceptionVector::BREAKPOINT => {
|
|
serial_println!("Breakpoint interrupt handled successfully.");
|
|
}
|
|
ExceptionVector::PAGE_FAULT => {
|
|
serial_println!("Page fault occurred! Error code: {:#x}", error_code);
|
|
halt_loop()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::super::instructions;
|
|
use super::*;
|
|
#[test_case]
|
|
fn test_interrupt_handler_trait() {
|
|
use crate::sync::LazyLock;
|
|
static IDT: LazyLock<InterruptDescriptorTable> =
|
|
crate::sync::LazyLock::new(InterruptDescriptorTable::new_default);
|
|
|
|
unsafe { IDT.load_unsafe() };
|
|
|
|
instructions::int3(); // Trigger a breakpoint interrupt (interrupt 3)
|
|
|
|
serial_println!("Breakpoint interrupt handled successfully.");
|
|
}
|
|
}
|