diff --git a/src/structures/idt.rs b/src/structures/idt.rs index da93db813..4a3d27bbb 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -685,6 +685,9 @@ pub type DivergingHandlerFuncWithErrCode = #[derive(Copy, Clone, Debug)] pub struct DivergingHandlerFuncWithErrCode(()); +/// A general handler function for an interrupt or an exception with the interrupt/exceptions's index and an optional error code. +pub type GeneralHandlerFunc = fn(InterruptStackFrame, index: u8, error_code: Option); + impl Entry { /// Creates a non-present IDT entry (but sets the must-be-one bits). #[inline] @@ -1120,10 +1123,234 @@ pub enum ExceptionVector { Security = 0x1E, } +#[cfg(all(feature = "instructions", feature = "abi_x86_interrupt"))] +#[macro_export] +/// Set a general handler in an [`InterruptDescriptorTable`]. +/// ``` +/// #![feature(abi_x86_interrupt)] +/// use x86_64::set_general_handler; +/// use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +/// +/// let mut idt = InterruptDescriptorTable::new(); +/// fn my_general_handler( +/// stack_frame: InterruptStackFrame, +/// index: u8, +/// error_code: Option, +/// ) { +/// todo!("handle irq {}", index) +/// } +/// +/// // set only one entry +/// # // there seems to be a bug in LLVM that causes rustc to crash on windows when compiling this test: +/// # // https://github.com/rust-osdev/x86_64/pull/285#issuecomment-962642984 +/// # #[cfg(not(windows))] +/// set_general_handler!(&mut idt, my_general_handler, 14); +/// +/// // set a range of entries +/// # // there seems to be a bug in LLVM that causes rustc to crash on windows when compiling this test: +/// # // https://github.com/rust-osdev/x86_64/pull/285#issuecomment-962642984 +/// # #[cfg(not(windows))] +/// set_general_handler!(&mut idt, my_general_handler, 32..64); +/// +/// // set all entries +/// # // there seems to be a bug in LLVM that causes rustc to crash on windows when compiling this test: +/// # // https://github.com/rust-osdev/x86_64/pull/285#issuecomment-962642984 +/// # #[cfg(not(windows))] +/// set_general_handler!(&mut idt, my_general_handler); +/// ``` +macro_rules! set_general_handler { + ($idt:expr, $handler:ident) => { + $crate::set_general_handler!($idt, $handler, 0..=255); + }; + ($idt:expr, $handler:ident, $idx:literal) => { + $crate::set_general_handler!($idt, $handler, $idx..=$idx); + }; + ($idt:expr, $handler:ident, $range:expr) => {{ + /// This constant is used to avoid spamming the same compilation error ~200 times + /// when the handler's signature is wrong. + /// If we just passed `$handler` to `set_general_handler_recursive_bits` + /// an error would be reported for every interrupt handler that tried to call it. + /// With `GENERAL_HANDLER` the error is only reported once for this constant. + const GENERAL_HANDLER: $crate::structures::idt::GeneralHandlerFunc = $handler; + + { + fn set_general_handler( + idt: &mut $crate::structures::idt::InterruptDescriptorTable, + range: impl ::core::ops::RangeBounds, + ) { + $crate::set_general_handler_recursive_bits!(idt, GENERAL_HANDLER, range); + } + set_general_handler($idt, $range); + } + }}; +} + +#[cfg(all(feature = "instructions", feature = "abi_x86_interrupt"))] +#[macro_export] +#[doc(hidden)] +/// We can't loop in macros, but we can use recursion. +/// This macro recursivly adds one more bit to it's arguments until we have 8 bits so that we can call set_general_handler_entry. +macro_rules! set_general_handler_recursive_bits { + // if we have 8 all bits, construct the index from the bits, check if the entry is in range and invoke the macro that sets the handler + ($idt:expr, $handler:ident, $range:expr, $bit7:tt, $bit6:tt, $bit5:tt, $bit4:tt, $bit3:tt, $bit2:tt, $bit1:tt, $bit0:tt) => {{ + const IDX: u8 = $bit0 | ($bit1 << 1) | ($bit2 << 2) | ($bit3 << 3) | ($bit4 << 4) | ($bit5 << 5) | ($bit6 << 6) | ($bit7 << 7); + + #[allow(unreachable_code)] + if $range.contains(&IDX) { + $crate::set_general_handler_entry!($idt, $handler, IDX, $bit7, $bit6, $bit5, $bit4, $bit3, $bit2, $bit1, $bit0); + } + }}; + // otherwise recursivly invoke the macro adding one more bit + ($idt:expr, $handler:ident, $range:expr $(, $bits:tt)*) => { + $crate::set_general_handler_recursive_bits!($idt, $handler, $range $(, $bits)*, 0); + $crate::set_general_handler_recursive_bits!($idt, $handler, $range $(, $bits)*, 1); + }; +} + +#[cfg(all(feature = "instructions", feature = "abi_x86_interrupt"))] +#[macro_export] +#[doc(hidden)] +macro_rules! set_general_handler_entry { + // special case entries that don't have the `HandlerFunc` signature + ($idt:expr, $handler:ident, $idx:expr, 0, 0, 0, 0, 1, 0, 0, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) -> ! { + $handler(frame, $idx.into(), Some(error_code)); + panic!("General handler returned on double fault"); + } + $idt.double_fault.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 0, 1, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.invalid_tss.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 0, 1, 1) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.segment_not_present.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 1, 0, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.stack_segment_fault.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 1, 0, 1) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.general_protection_fault.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 1, 1, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: $crate::structures::idt::PageFaultErrorCode, + ) { + $handler(frame, IDX.into(), Some(error_code.bits())); + } + $idt.page_fault.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 0, 0, 0, 1) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.alignment_check.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 0, 0, 1, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + ) -> ! { + $handler(frame, $idx.into(), None); + panic!("General handler returned on machine check exception"); + } + $idt.machine_check.set_handler_fn(handler); + }}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 1, 0, 1) => { + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.vmm_communication_exception.set_handler_fn(handler); + }; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 1, 1, 0) => {{ + extern "x86-interrupt" fn handler( + frame: $crate::structures::idt::InterruptStackFrame, + error_code: u64, + ) { + $handler(frame, $idx.into(), Some(error_code)); + } + $idt.security_exception.set_handler_fn(handler); + }}; + + // reserved_1 + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 0, 1, 1, 1, 1) => {}; + // reserved_2 + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 0, 1, 0, 1) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 0, 1, 1, 0) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 0, 1, 1, 1) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 0, 0, 0) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 0, 0, 1) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 0, 1, 0) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 0, 1, 1) => {}; + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 1, 0, 0) => {}; + // reserved_3 + ($idt:expr, $handler:ident, $idx:ident, 0, 0, 0, 1, 1, 1, 1, 1) => {}; + + // set entries with `HandlerFunc` signature + ($idt:expr, $handler:ident, $idx:ident $(, $_bits:tt)*) => {{ + extern "x86-interrupt" fn handler(frame: $crate::structures::idt::InterruptStackFrame) { + $handler(frame, $idx.into(), None); + } + $idt[$idx as usize].set_handler_fn(handler); + }}; +} + #[cfg(test)] mod test { use super::*; + fn entry_present(idt: &InterruptDescriptorTable, index: usize) -> bool { + let options = match index { + 8 => &idt.double_fault.options, + 10 => &idt.invalid_tss.options, + 11 => &idt.segment_not_present.options, + 12 => &idt.stack_segment_fault.options, + 13 => &idt.general_protection_fault.options, + 14 => &idt.page_fault.options, + 15 => &idt.reserved_1.options, + 17 => &idt.alignment_check.options, + 18 => &idt.machine_check.options, + i @ 21..=28 => &idt.reserved_2[i - 21].options, + 29 => &idt.vmm_communication_exception.options, + 30 => &idt.security_exception.options, + 31 => &idt.reserved_3.options, + other => &idt[other].options, + }; + options.0.get_bit(15) + } + #[test] fn size_test() { use core::mem::size_of; @@ -1131,6 +1358,55 @@ mod test { assert_eq!(size_of::(), 256 * 16); } + #[cfg(all(feature = "instructions", feature = "abi_x86_interrupt"))] + // there seems to be a bug in LLVM that causes rustc to crash on windows when compiling this test: + // https://github.com/rust-osdev/x86_64/pull/285#issuecomment-962642984 + #[cfg(not(windows))] + #[test] + fn default_handlers() { + fn general_handler( + _stack_frame: InterruptStackFrame, + _index: u8, + _error_code: Option, + ) { + } + + let mut idt = InterruptDescriptorTable::new(); + set_general_handler!(&mut idt, general_handler, 0); + for i in 0..256 { + if i == 0 { + assert!(entry_present(&idt, i)); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler, 14); + for i in 0..256 { + if i == 0 || i == 14 { + assert!(entry_present(&idt, i)); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler, 32..64); + for i in 1..256 { + if i == 0 || i == 14 || (32..64).contains(&i) { + assert!(entry_present(&idt, i), "{}", i); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler); + for i in 0..256 { + if i == 15 || i == 31 || (21..=28).contains(&i) { + // reserved entries should not be set + assert!(!entry_present(&idt, i)); + } else { + assert!(entry_present(&idt, i)); + } + } + } + #[test] fn entry_derive_test() { fn foo(_: impl Clone + Copy + PartialEq + fmt::Debug) {}