From 92d7c69a721170409e63838f89bcf4fb427ef2e7 Mon Sep 17 00:00:00 2001 From: Masato Imai Date: Sat, 14 Jun 2025 13:39:57 +0000 Subject: [PATCH] wip: external interrupt --- src/interrupts.rs | 252 +++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 6 ++ src/vmm/io.rs | 2 + src/vmm/vcpu.rs | 109 +++++++++++++++++++- src/vmm/vmcs.rs | 35 +++++++ 5 files changed, 397 insertions(+), 7 deletions(-) diff --git a/src/interrupts.rs b/src/interrupts.rs index 8eb999d..e0b9549 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -1,9 +1,147 @@ use lazy_static::lazy_static; use pic8259::ChainedPics; +use spin::Mutex; use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; use crate::{gdt, print, println}; +/// Interrupt context containing interrupt information +#[derive(Debug)] +pub struct InterruptContext { + pub vector: u8, + pub instruction_pointer: u64, + pub code_segment: u64, + pub cpu_flags: u64, + pub stack_pointer: u64, + pub stack_segment: u64, +} + +/// Subscriber callback function type (simple callback without context) +pub type SubscriberCallback = fn(&InterruptContext); + +/// Advanced subscriber callback with user context +pub type AdvancedSubscriberCallback = fn(*mut core::ffi::c_void, &InterruptContext); + +/// Interrupt subscriber variants +#[derive(Debug, Clone, Copy)] +pub enum Subscriber { + Simple { + callback: SubscriberCallback, + }, + Advanced { + callback: AdvancedSubscriberCallback, + context: *mut core::ffi::c_void, + }, +} + +// Safety: We ensure thread safety by using Mutex to protect the subscribers array +unsafe impl Send for Subscriber {} +unsafe impl Sync for Subscriber {} + +const MAX_SUBSCRIBERS: usize = 10; + +/// Global subscribers array +static SUBSCRIBERS: Mutex<[Option; MAX_SUBSCRIBERS]> = + Mutex::new([None; MAX_SUBSCRIBERS]); + +/// Subscribe to interrupts with a callback function +pub fn subscribe(callback: SubscriberCallback) -> Result<(), &'static str> { + let mut subscribers = SUBSCRIBERS.lock(); + + for i in 0..MAX_SUBSCRIBERS { + if subscribers[i].is_none() { + subscribers[i] = Some(Subscriber::Simple { callback }); + return Ok(()); + } + } + + Err("Subscriber array full") +} + +/// Subscribe to interrupts with an advanced callback that receives user context +pub fn subscribe_with_context( + callback: AdvancedSubscriberCallback, + context: *mut core::ffi::c_void, +) -> Result<(), &'static str> { + let mut subscribers = SUBSCRIBERS.lock(); + + for i in 0..MAX_SUBSCRIBERS { + if subscribers[i].is_none() { + subscribers[i] = Some(Subscriber::Advanced { callback, context }); + return Ok(()); + } + } + + Err("Subscriber array full") +} + +/// Unsubscribe from interrupts +pub fn unsubscribe(callback: SubscriberCallback) -> Result<(), &'static str> { + let mut subscribers = SUBSCRIBERS.lock(); + + for i in 0..MAX_SUBSCRIBERS { + if let Some(subscriber) = subscribers[i] { + match subscriber { + Subscriber::Simple { callback: cb } => { + if cb as *const () == callback as *const () { + subscribers[i] = None; + return Ok(()); + } + } + _ => {} // Skip advanced subscribers + } + } + } + + Err("Subscriber not found") +} + +/// Unsubscribe from interrupts (advanced callback with context) +pub fn unsubscribe_with_context( + callback: AdvancedSubscriberCallback, + context: *mut core::ffi::c_void, +) -> Result<(), &'static str> { + let mut subscribers = SUBSCRIBERS.lock(); + + for i in 0..MAX_SUBSCRIBERS { + if let Some(subscriber) = subscribers[i] { + match subscriber { + Subscriber::Advanced { + callback: cb, + context: ctx, + } => { + if cb as *const () == callback as *const () && ctx == context { + subscribers[i] = None; + return Ok(()); + } + } + _ => {} // Skip simple subscribers + } + } + } + + Err("Subscriber not found") +} + +/// Dispatch interrupt to all subscribers +fn dispatch_to_subscribers(context: &InterruptContext) { + let subscribers = SUBSCRIBERS.lock(); + + for subscriber in subscribers.iter().flatten() { + match subscriber { + Subscriber::Simple { callback } => { + callback(context); + } + Subscriber::Advanced { + callback, + context: user_context, + } => { + callback(*user_context, context); + } + } + } +} + lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); @@ -28,6 +166,18 @@ pub fn init_idt() { } extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + let context = InterruptContext { + vector: 3, // Breakpoint exception vector + instruction_pointer: stack_frame.instruction_pointer.as_u64(), + code_segment: stack_frame.code_segment, + cpu_flags: stack_frame.cpu_flags, + stack_pointer: stack_frame.stack_pointer.as_u64(), + stack_segment: stack_frame.stack_segment, + }; + + // Notify subscribers first + dispatch_to_subscribers(&context); + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); } @@ -39,6 +189,18 @@ extern "x86-interrupt" fn double_fault_handler( } extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { + let context = InterruptContext { + vector: 6, // Invalid opcode exception vector + instruction_pointer: stack_frame.instruction_pointer.as_u64(), + code_segment: stack_frame.code_segment, + cpu_flags: stack_frame.cpu_flags, + stack_pointer: stack_frame.stack_pointer.as_u64(), + stack_segment: stack_frame.stack_segment, + }; + + // Notify subscribers first + dispatch_to_subscribers(&context); + panic!("EXCEPTION: INVALID OPCODE\n{:#?}", stack_frame); } @@ -51,6 +213,18 @@ extern "x86-interrupt" fn page_fault_handler( ) { use x86_64::registers::control::Cr2; + let context = InterruptContext { + vector: 14, // Page fault exception vector + instruction_pointer: stack_frame.instruction_pointer.as_u64(), + code_segment: stack_frame.code_segment, + cpu_flags: stack_frame.cpu_flags, + stack_pointer: stack_frame.stack_pointer.as_u64(), + stack_segment: stack_frame.stack_segment, + }; + + // Notify subscribers first + dispatch_to_subscribers(&context); + println!("EXCEPTION: PAGE FAULT"); println!("Accessed Address: {:?}", Cr2::read()); println!("Error Code: {:?}", error_code); @@ -81,18 +255,43 @@ impl InterruptIndex { } } -extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { +extern "x86-interrupt" fn timer_interrupt_handler(stack_frame: InterruptStackFrame) { + let context = InterruptContext { + vector: InterruptIndex::Timer.as_u8(), + instruction_pointer: stack_frame.instruction_pointer.as_u64(), + code_segment: stack_frame.code_segment, + cpu_flags: stack_frame.cpu_flags, + stack_pointer: stack_frame.stack_pointer.as_u64(), + stack_segment: stack_frame.stack_segment, + }; + + // Notify subscribers first + dispatch_to_subscribers(&context); + + // Then handle the interrupt normally unsafe { PICS.lock() .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); } } -extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { +extern "x86-interrupt" fn keyboard_interrupt_handler(stack_frame: InterruptStackFrame) { use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; use spin::Mutex; use x86_64::instructions::port::Port; + let context = InterruptContext { + vector: InterruptIndex::Keyboard.as_u8(), + instruction_pointer: stack_frame.instruction_pointer.as_u64(), + code_segment: stack_frame.code_segment, + cpu_flags: stack_frame.cpu_flags, + stack_pointer: stack_frame.stack_pointer.as_u64(), + stack_segment: stack_frame.stack_segment, + }; + + // Notify subscribers first + dispatch_to_subscribers(&context); + lazy_static! { static ref KEYBOARD: Mutex> = Mutex::new(Keyboard::new( @@ -121,7 +320,56 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac } } +pub fn example_subscriber(context: &InterruptContext) { + println!( + "Subscriber: Interrupt {} occurred at {:#x}", + context.vector, context.instruction_pointer + ); +} + +pub fn vmm_subscriber(vcpu_ptr: *mut core::ffi::c_void, context: &InterruptContext) { + if !vcpu_ptr.is_null() { + let vcpu = unsafe { &mut *(vcpu_ptr as *mut crate::vmm::vcpu::VCpu) }; + + if 0x20 <= context.vector && context.vector < 0x20 + 16 { + let irq = context.vector - 0x20; + vcpu.pending_irq |= 1 << irq; + } + } +} + #[test_case] fn test_breakpoint_exception() { x86_64::instructions::interrupts::int3(); } + +#[test_case] +fn test_subscriber_functionality() { + // Test subscribing to interrupts + let result = subscribe(example_subscriber); + assert!(result.is_ok()); + + // Test that subscriber array has limits + for _ in 0..MAX_SUBSCRIBERS { + let _ = subscribe(example_subscriber); + } + // This should fail because the array is full + let result = subscribe(example_subscriber); + assert!(result.is_err()); +} + +#[test_case] +fn test_advanced_subscriber_functionality() { + // Test subscribing with context + let vcpu_dummy = 0x12345678usize as *mut core::ffi::c_void; + let result = subscribe_with_context(vmm_subscriber, vcpu_dummy); + assert!(result.is_ok()); + + // Test unsubscribing with context + let result = unsubscribe_with_context(vmm_subscriber, vcpu_dummy); + assert!(result.is_ok()); + + // Unsubscribing again should fail + let result = unsubscribe_with_context(vmm_subscriber, vcpu_dummy); + assert!(result.is_err()); +} diff --git a/src/lib.rs b/src/lib.rs index 26759b6..c3064a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,12 @@ extern crate alloc; pub mod allocator; pub mod gdt; pub mod interrupts; + +// Re-export commonly used interrupt functionality +pub use interrupts::{ + subscribe, unsubscribe, subscribe_with_context, unsubscribe_with_context, + InterruptContext, SubscriberCallback, AdvancedSubscriberCallback +}; pub mod memory; pub mod serial; pub mod vga_buffer; diff --git a/src/vmm/io.rs b/src/vmm/io.rs index 14519ca..3cabb38 100644 --- a/src/vmm/io.rs +++ b/src/vmm/io.rs @@ -6,6 +6,8 @@ pub struct Serial { pub mcr: u8, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum InitPhase { Uninitialized, Phase1, diff --git a/src/vmm/vcpu.rs b/src/vmm/vcpu.rs index ee4da48..f676793 100644 --- a/src/vmm/vcpu.rs +++ b/src/vmm/vcpu.rs @@ -1,4 +1,5 @@ use core::{ + arch::asm, arch::x86_64::{_xgetbv, _xsetbv}, convert::TryInto, u64, u8, @@ -18,15 +19,17 @@ use x86_64::{ }; use crate::{ - info, + hlt_loop, info, + interrupts::vmm_subscriber, memory::BootInfoFrameAllocator, + subscribe_with_context, vmm::{ cpuid, cr, fpu, - io::{self, Serial, PIC}, + io::{self, InitPhase, Serial, PIC}, msr, qual::{QualCr, QualIo}, vmcs::{ - DescriptorType, EntryControls, Granularity, PrimaryExitControls, + DescriptorType, EntryControls, EntryIntrInfo, Granularity, PrimaryExitControls, PrimaryProcessorBasedVmExecutionControls, SecondaryProcessorBasedVmExecutionControls, SegmentRights, VmxExitReason, }, @@ -61,6 +64,7 @@ pub struct VCpu { pub io_bitmap_a: x86_64::structures::paging::PhysFrame, pub io_bitmap_b: x86_64::structures::paging::PhysFrame, pub pic: PIC, + pub pending_irq: u16, } const TEMP_STACK_SIZE: usize = 4096; @@ -95,6 +99,7 @@ impl VCpu { io_bitmap_a, io_bitmap_b, pic: PIC::new(), + pending_irq: 0, } } @@ -305,6 +310,7 @@ impl VCpu { pin_exec_ctrl.0 |= (reserved_bits & 0xFFFFFFFF) as u32; pin_exec_ctrl.0 &= (reserved_bits >> 32) as u32; + pin_exec_ctrl.set_external_interrupt_exiting(true); pin_exec_ctrl.write(); @@ -320,7 +326,7 @@ impl VCpu { primary_exec_ctrl.0 |= (reserved_bits & 0xFFFFFFFF) as u32; primary_exec_ctrl.0 &= (reserved_bits >> 32) as u32; - primary_exec_ctrl.set_hlt(true); + primary_exec_ctrl.set_hlt(false); primary_exec_ctrl.set_activate_secondary_controls(true); primary_exec_ctrl.set_use_tpr_shadow(true); primary_exec_ctrl.set_use_msr_bitmap(false); @@ -658,6 +664,11 @@ impl VCpu { pub fn vm_loop(&mut self) -> ! { info!("Entering VM loop"); + let vcpu: &mut VCpu = self; + let vcpu_ptr = vcpu as *mut VCpu as *mut core::ffi::c_void; + subscribe_with_context(vmm_subscriber, vcpu_ptr) + .expect("Failed to subscribe to vmm_subscriber"); + loop { if let Err(err) = self.vmentry() { info!("VMEntry failed: {}", err.as_str()); @@ -735,6 +746,73 @@ impl VCpu { Ok(()) } + fn inject_external_interrupt(&mut self) -> Result { + info!("Injecting external interrupt"); + let pending = self.pending_irq; + + if pending == 0 { + return Ok(false); + } + + if self.pic.primary_phase != InitPhase::Initialized { + return Ok(false); + } + + let eflags = unsafe { vmread(vmcs::guest::RFLAGS) }?; + if eflags >> 9 & 1 == 0 { + return Ok(false); + } + + let is_secondary_masked = (self.pic.primary_mask >> 2) & 1 != 0; + + for i in 0..16 { + if is_secondary_masked && i >= 8 { + break; + } + + let irq_bit = 1 << i; + if pending & irq_bit == 0 { + continue; + } + + let delta = if i < 8 { i } else { i - 8 }; + let is_masked = if i < 8 { + (self.pic.primary_mask >> delta) & 1 != 0 + } else { + let is_ieq_masked = (self.pic.secondary_mask >> delta) & 1 != 0; + is_secondary_masked || is_ieq_masked + }; + + if is_masked { + continue; + } + + let mut interrupt_info = EntryIntrInfo(0); + interrupt_info.set_vector( + delta as u32 + + if i < 8 { + self.pic.primary_base as u32 + } else { + self.pic.secondary_base as u32 + }, + ); + interrupt_info.set_type(0); + interrupt_info.set_ec_available(false); + interrupt_info.set_valid(true); + unsafe { + vmwrite( + vmcs::control::VMENTRY_INTERRUPTION_INFO_FIELD, + interrupt_info.0 as u64, + )?; + } + + self.pending_irq &= !irq_bit; + return Ok(true); + } + + Ok(false) + } + #[no_mangle] unsafe extern "C" fn set_host_stack(rsp: u64) { vmwrite(vmcs::host::RSP, rsp).unwrap(); @@ -773,7 +851,19 @@ impl VCpu { let exit_reason: VmxExitReason = basic_reason.try_into().unwrap(); match exit_reason { VmxExitReason::HLT => { - info!("HLT instruction executed"); + while self.inject_external_interrupt().is_err() { + unsafe { + asm!("sti"); + asm!("nop"); + asm!("cli"); + } + } + + unsafe { + vmwrite(vmcs::guest::ACTIVITY_STATE, 0).unwrap(); + vmwrite(vmcs::guest::INTERRUPTIBILITY_STATE, 0).unwrap(); + } + self.step_next_inst().unwrap(); } VmxExitReason::CPUID => { cpuid::handle_cpuid_exit(self); @@ -812,6 +902,15 @@ impl VCpu { io::handle_io(self, qual_io); self.step_next_inst().unwrap(); } + VmxExitReason::EXTERNAL_INTERRUPT => { + unsafe { + asm!("sti"); + asm!("nop"); + asm!("cli"); + } + self.inject_external_interrupt().unwrap(); + self.step_next_inst().unwrap(); + } _ => { panic!("VMExit reason: {:?}", exit_reason); } diff --git a/src/vmm/vmcs.rs b/src/vmm/vmcs.rs index f904b93..2a9548e 100644 --- a/src/vmm/vmcs.rs +++ b/src/vmm/vmcs.rs @@ -653,3 +653,38 @@ impl VmxLeaf { } } } + +/* +pub const EntryIntrInfo = packed struct(u32) { + vector: u8, + type: Type, + ec_available: bool, + _notused: u19 = 0, + valid: bool, + + const Type = enum(u3) { + external = 0, + _unused1 = 1, + nmi = 2, + hw = 3, + _unused2 = 4, + priviledged_sw = 5, + exception = 6, + _unused3 = 7, + }; + + const Kind = enum { + entry, + exit, + }; +}; */ + +bitfield! { + pub struct EntryIntrInfo(u32); + impl Debug; + + pub vector, set_vector: 7, 0; + pub typ, set_type: 10, 8; + pub ec_available, set_ec_available: 11; + pub valid, set_valid: 31; +}