wip: external interrupt

This commit is contained in:
Masato Imai
2025-06-14 13:39:57 +00:00
parent 50325ff4e2
commit 92d7c69a72
5 changed files with 397 additions and 7 deletions

View File

@ -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<Subscriber>; 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<Keyboard<layouts::Us104Key, ScancodeSet1>> =
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());
}

View File

@ -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;

View File

@ -6,6 +6,8 @@ pub struct Serial {
pub mcr: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitPhase {
Uninitialized,
Phase1,

View File

@ -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<bool, VmFail> {
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);
}

View File

@ -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;
}