mirror of
https://github.com/mii443/nel_os.git
synced 2025-08-22 16:15:38 +00:00
wip: external interrupt
This commit is contained in:
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -6,6 +6,8 @@ pub struct Serial {
|
||||
pub mcr: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
pub enum InitPhase {
|
||||
Uninitialized,
|
||||
Phase1,
|
||||
|
109
src/vmm/vcpu.rs
109
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<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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user