I/O Emulation

This commit is contained in:
Masato Imai
2025-06-13 19:39:35 +00:00
parent df612c696f
commit 50325ff4e2
5 changed files with 182 additions and 80 deletions

View File

@ -1,5 +1,3 @@
use core::fmt::Write;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use spin::Mutex; use spin::Mutex;
use uart_16550::SerialPort; use uart_16550::SerialPort;
@ -25,19 +23,6 @@ pub fn _print(args: ::core::fmt::Arguments) {
}); });
} }
#[inline(always)]
pub fn write_byte(byte: u8) {
use x86_64::instructions::interrupts;
if interrupts::are_enabled() {
interrupts::without_interrupts(|| {
SERIAL1.lock().send(byte);
});
} else {
SERIAL1.lock().send(byte);
}
}
#[macro_export] #[macro_export]
macro_rules! serial_print { macro_rules! serial_print {
($($arg:tt)*) => { ($($arg:tt)*) => {

View File

@ -4,7 +4,6 @@ use lazy_static::lazy_static;
use spin::Mutex; use spin::Mutex;
use volatile::Volatile; use volatile::Volatile;
use crate::serial::SERIAL1;
lazy_static! { lazy_static! {
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer { pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
@ -37,15 +36,11 @@ macro_rules! error {
#[doc(hidden)] #[doc(hidden)]
pub fn _print(args: fmt::Arguments) { pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
use x86_64::instructions::interrupts; use x86_64::instructions::interrupts;
interrupts::without_interrupts(|| { interrupts::without_interrupts(|| {
//WRITER.lock().write_fmt(args).unwrap(); //WRITER.lock().write_fmt(args).unwrap();
SERIAL1 crate::serial::_print(args);
.lock()
.write_fmt(args)
.expect("Printing to serial failed");
}); });
} }

View File

@ -125,7 +125,7 @@ pub fn handle_cpuid_exit(vcpu: &mut VCpu) {
pae: true, pae: true,
mce: false, mce: false,
cx8: true, cx8: true,
apic: true, apic: false,
_reserved_0: false, _reserved_0: false,
sep: true, sep: true,
mtrr: false, mtrr: false,

View File

@ -1,9 +1,4 @@
use x86::io::inb; use crate::vmm::{qual::QualIo, vcpu::VCpu};
use crate::{
serial,
vmm::{qual::QualIo, vcpu::VCpu},
};
#[derive(Default)] #[derive(Default)]
pub struct Serial { pub struct Serial {
@ -11,6 +6,36 @@ pub struct Serial {
pub mcr: u8, pub mcr: u8,
} }
pub enum InitPhase {
Uninitialized,
Phase1,
Phase2,
Phase3,
Initialized,
}
pub struct PIC {
pub primary_mask: u8,
pub secondary_mask: u8,
pub primary_phase: InitPhase,
pub secondary_phase: InitPhase,
pub primary_base: u8,
pub secondary_base: u8,
}
impl PIC {
pub fn new() -> Self {
Self {
primary_mask: 0xFF,
secondary_mask: 0xFF,
primary_phase: InitPhase::Uninitialized,
secondary_phase: InitPhase::Uninitialized,
primary_base: 0,
secondary_base: 0,
}
}
}
pub fn handle_io(vcpu: &mut VCpu, qual: QualIo) { pub fn handle_io(vcpu: &mut VCpu, qual: QualIo) {
match qual.direction() { match qual.direction() {
0 => { 0 => {
@ -26,59 +51,82 @@ pub fn handle_io(vcpu: &mut VCpu, qual: QualIo) {
pub fn handle_io_in(vcpu: &mut VCpu, qual: QualIo) { pub fn handle_io_in(vcpu: &mut VCpu, qual: QualIo) {
let regs = &mut vcpu.guest_registers; let regs = &mut vcpu.guest_registers;
match qual.port() { match qual.port() {
0x0CF8..0x0CFF => { 0x0CF8..=0x0CFF => regs.rax = 0,
regs.rax = 0; 0xC000..=0xCFFF => {} //ignore
} 0x20..=0x21 => handle_pic_in(vcpu, qual),
0xC000..0xCFFF => {} //ignore 0xA0..=0xA1 => handle_pic_in(vcpu, qual),
0x0070..=0x0071 => regs.rax = 0,
0x03F..0x03FF => handle_serial_in(vcpu, qual), _ => regs.rax = 0,
_ => {
panic!("IO in: invalid port: {:#x}", qual.port());
}
} }
} }
pub fn handle_io_out(vcpu: &mut VCpu, qual: QualIo) { pub fn handle_io_out(vcpu: &mut VCpu, qual: QualIo) {
let regs = &vcpu.guest_registers;
match qual.port() { match qual.port() {
0x0CF8..0x0CFF => {} //ignore 0x0CF8..=0x0CFF => {} //ignore
0xC000..0xCFFF => {} //ignore 0xC000..=0xCFFF => {} //ignore
0x03F8..0x03FF => handle_serial_out(vcpu, qual), 0x20..=0x21 => handle_pic_out(vcpu, qual),
_ => { 0xA0..=0xA1 => handle_pic_out(vcpu, qual),
panic!("IO out: invalid port: {:#x}", qual.port()); 0x0070..=0x0071 => {} //ignore
} _ => {}
} }
} }
fn handle_serial_in(vcpu: &mut VCpu, qual: QualIo) { pub fn handle_pic_in(vcpu: &mut VCpu, qual: QualIo) {
let regs = &mut vcpu.guest_registers; let regs = &mut vcpu.guest_registers;
match qual.port() { match qual.port() {
0x3F8 => regs.rax = unsafe { inb(qual.port()).into() }, 0x21 => match vcpu.pic.primary_phase {
0x3F9 => regs.rax = vcpu.serial.ier as u64, InitPhase::Uninitialized | InitPhase::Initialized => {
0x3FA => regs.rax = unsafe { inb(qual.port()).into() }, regs.rax = vcpu.pic.primary_mask as u64;
0x3FB => regs.rax = 0, }
0x3FC => regs.rax = vcpu.serial.mcr as u64, _ => {}
0x3FD => regs.rax = unsafe { inb(qual.port()).into() }, },
0x3FE => regs.rax = unsafe { inb(qual.port()).into() }, 0xA1 => match vcpu.pic.secondary_phase {
0x3FF => regs.rax = 0, InitPhase::Uninitialized | InitPhase::Initialized => {
_ => { regs.rax = vcpu.pic.secondary_mask as u64;
panic!("Serial in: invalid port: {:#x}", qual.port()); }
} _ => {}
},
_ => {}
} }
} }
fn handle_serial_out(vcpu: &mut VCpu, qual: QualIo) { pub fn handle_pic_out(vcpu: &mut VCpu, qual: QualIo) {
let regs = &mut vcpu.guest_registers; let regs = &mut vcpu.guest_registers;
let pic = &mut vcpu.pic;
let dx = regs.rax as u8;
match qual.port() { match qual.port() {
0x3F8 => serial::write_byte(regs.rax as u8), 0x20 => match dx {
0x3F9 => vcpu.serial.ier = regs.rax as u8, 0x11 => pic.primary_phase = InitPhase::Phase1,
0x3FA => {} 0x60..=0x67 => {}
0x3FB => {} _ => panic!("Primary PIC command: {:#x}", dx),
0x3FC => vcpu.serial.mcr = regs.rax as u8, },
0x3FD => {} 0x21 => match pic.primary_phase {
0x3FF => {} InitPhase::Uninitialized | InitPhase::Initialized => pic.primary_mask = dx,
_ => { InitPhase::Phase1 => {
panic!("Serial out: invalid port: {:#x}", qual.port()); pic.primary_base = dx;
} pic.primary_phase = InitPhase::Phase2;
}
InitPhase::Phase2 => {
pic.primary_phase = InitPhase::Phase3;
}
InitPhase::Phase3 => pic.primary_phase = InitPhase::Initialized,
},
0xA0 => match dx {
0x11 => pic.secondary_phase = InitPhase::Phase1,
0x60..=0x67 => {}
_ => panic!("Secondary PIC command: {:#x}", dx),
},
0xA1 => match pic.secondary_phase {
InitPhase::Uninitialized | InitPhase::Initialized => pic.secondary_mask = dx,
InitPhase::Phase1 => {
pic.secondary_base = dx;
pic.secondary_phase = InitPhase::Phase2;
}
InitPhase::Phase2 => {
pic.secondary_phase = InitPhase::Phase3;
}
InitPhase::Phase3 => pic.secondary_phase = InitPhase::Initialized,
},
_ => {}
} }
} }

View File

@ -1,6 +1,7 @@
use core::{ use core::{
arch::x86_64::{_xgetbv, _xsetbv}, arch::x86_64::{_xgetbv, _xsetbv},
u64, convert::TryInto,
u64, u8,
}; };
use x86::{ use x86::{
@ -10,20 +11,24 @@ use x86::{
msr::{rdmsr, IA32_EFER, IA32_FS_BASE}, msr::{rdmsr, IA32_EFER, IA32_FS_BASE},
vmx::{vmcs, VmFail}, vmx::{vmcs, VmFail},
}; };
use x86_64::{registers::control::Cr4Flags, structures::paging::OffsetPageTable, VirtAddr}; use x86_64::{
registers::control::Cr4Flags,
structures::paging::{FrameAllocator, OffsetPageTable},
VirtAddr,
};
use crate::{ use crate::{
info, info,
memory::BootInfoFrameAllocator, memory::BootInfoFrameAllocator,
vmm::{ vmm::{
cpuid, cr, fpu, cpuid, cr, fpu,
io::{self, Serial}, io::{self, Serial, PIC},
msr, msr,
qual::{QualCr, QualIo}, qual::{QualCr, QualIo},
vmcs::{ vmcs::{
DescriptorType, EntryControls, Granularity, PrimaryExitControls, DescriptorType, EntryControls, Granularity, PrimaryExitControls,
PrimaryProcessorBasedVmExecutionControls, SecondaryProcessorBasedVmExecutionControls, PrimaryProcessorBasedVmExecutionControls, SecondaryProcessorBasedVmExecutionControls,
SegmentRights, VmxExitInfo, VmxExitReason, SegmentRights, VmxExitReason,
}, },
}, },
}; };
@ -53,6 +58,9 @@ pub struct VCpu {
pub xcr0: XCR0, pub xcr0: XCR0,
pub host_xcr0: u64, pub host_xcr0: u64,
pub serial: Serial, pub serial: Serial,
pub io_bitmap_a: x86_64::structures::paging::PhysFrame,
pub io_bitmap_b: x86_64::structures::paging::PhysFrame,
pub pic: PIC,
} }
const TEMP_STACK_SIZE: usize = 4096; const TEMP_STACK_SIZE: usize = 4096;
@ -66,6 +74,10 @@ impl VCpu {
let ept = EPT::new(frame_allocator); let ept = EPT::new(frame_allocator);
let eptp = EPTP::new(&ept.root_table); let eptp = EPTP::new(&ept.root_table);
// Allocate I/O bitmaps (4KB each)
let io_bitmap_a = frame_allocator.allocate_frame().unwrap();
let io_bitmap_b = frame_allocator.allocate_frame().unwrap();
VCpu { VCpu {
vmxon, vmxon,
vmcs, vmcs,
@ -80,6 +92,9 @@ impl VCpu {
xcr0: XCR0(3), xcr0: XCR0(3),
host_xcr0: 0, host_xcr0: 0,
serial: Serial::default(), serial: Serial::default(),
io_bitmap_a,
io_bitmap_b,
pic: PIC::new(),
} }
} }
@ -99,6 +114,7 @@ impl VCpu {
self.setup_exit_ctrls().unwrap(); self.setup_exit_ctrls().unwrap();
self.setup_host_state().unwrap(); self.setup_host_state().unwrap();
self.setup_guest_state().unwrap(); self.setup_guest_state().unwrap();
self.setup_io_bitmaps();
self.setup_guest_memory(frame_allocator); self.setup_guest_memory(frame_allocator);
self.register_msrs(&mapper); self.register_msrs(&mapper);
} }
@ -304,11 +320,12 @@ impl VCpu {
primary_exec_ctrl.0 |= (reserved_bits & 0xFFFFFFFF) as u32; primary_exec_ctrl.0 |= (reserved_bits & 0xFFFFFFFF) as u32;
primary_exec_ctrl.0 &= (reserved_bits >> 32) as u32; primary_exec_ctrl.0 &= (reserved_bits >> 32) as u32;
primary_exec_ctrl.set_hlt(false); primary_exec_ctrl.set_hlt(true);
primary_exec_ctrl.set_activate_secondary_controls(true); primary_exec_ctrl.set_activate_secondary_controls(true);
primary_exec_ctrl.set_use_tpr_shadow(true); primary_exec_ctrl.set_use_tpr_shadow(true);
primary_exec_ctrl.set_use_msr_bitmap(false); primary_exec_ctrl.set_use_msr_bitmap(false);
primary_exec_ctrl.set_unconditional_io(true); primary_exec_ctrl.set_unconditional_io(false);
primary_exec_ctrl.set_use_io_bitmap(true);
primary_exec_ctrl.write(); primary_exec_ctrl.write();
@ -395,6 +412,59 @@ impl VCpu {
Ok(()) Ok(())
} }
pub fn setup_io_bitmaps(&mut self) {
info!("Setting up I/O bitmaps");
let bitmap_a_vaddr = self.io_bitmap_a.start_address().as_u64() + self.phys_mem_offset;
let bitmap_b_vaddr = self.io_bitmap_b.start_address().as_u64() + self.phys_mem_offset;
unsafe {
core::ptr::write_bytes(bitmap_a_vaddr as *mut u8, u8::MAX, 4096);
core::ptr::write_bytes(bitmap_b_vaddr as *mut u8, u8::MAX, 4096);
}
let bitmap_a = unsafe { core::slice::from_raw_parts_mut(bitmap_a_vaddr as *mut u8, 4096) };
let bitmap_b = unsafe { core::slice::from_raw_parts_mut(bitmap_b_vaddr as *mut u8, 4096) };
self.set_io_ports(bitmap_a, bitmap_b, 0x02F8..=0x03FF);
self.set_io_ports(bitmap_a, bitmap_b, 0x0040..=0x0047);
unsafe {
vmwrite(
vmcs::control::IO_BITMAP_A_ADDR_FULL,
self.io_bitmap_a.start_address().as_u64(),
)
.unwrap();
vmwrite(
vmcs::control::IO_BITMAP_B_ADDR_FULL,
self.io_bitmap_b.start_address().as_u64(),
)
.unwrap();
}
info!("I/O bitmaps configured - PCI ports 0xC000-0xCFFF will trigger VM exits");
}
fn set_io_ports(
&self,
bitmap_a: &mut [u8],
bitmap_b: &mut [u8],
ports: core::ops::RangeInclusive<u16>,
) {
for port in ports {
if port <= 0x7FFF {
let byte_index = port as usize / 8;
let bit_index = port as usize % 8;
bitmap_a[byte_index] &= !(1 << bit_index);
} else {
let adjusted_port = port - 0x8000;
let byte_index = adjusted_port as usize / 8;
let bit_index = adjusted_port as usize % 8;
bitmap_b[byte_index] &= !(1 << bit_index);
}
}
}
pub fn setup_host_state(&mut self) -> Result<(), VmFail> { pub fn setup_host_state(&mut self) -> Result<(), VmFail> {
info!("Setting up host state"); info!("Setting up host state");
unsafe { unsafe {
@ -681,10 +751,11 @@ impl VCpu {
} }
fn vmexit_handler(&mut self) { fn vmexit_handler(&mut self) {
let info = VmxExitInfo::read(); let exit_reason_raw = unsafe { vmread(vmcs::ro::EXIT_REASON).unwrap() as u32 };
if info.entry_failure() { if (exit_reason_raw & (1 << 31)) != 0 {
let reason = info.0 & 0xFF; // VM-entry failure
let reason = exit_reason_raw & 0xFF;
match reason { match reason {
33 => { 33 => {
info!(" Reason: VM-entry failure due to invalid guest state"); info!(" Reason: VM-entry failure due to invalid guest state");
@ -698,7 +769,9 @@ impl VCpu {
_ => {} _ => {}
} }
} else { } else {
match info.get_reason() { let basic_reason = (exit_reason_raw & 0xFFFF) as u16;
let exit_reason: VmxExitReason = basic_reason.try_into().unwrap();
match exit_reason {
VmxExitReason::HLT => { VmxExitReason::HLT => {
info!("HLT instruction executed"); info!("HLT instruction executed");
} }
@ -734,12 +807,13 @@ impl VCpu {
} }
VmxExitReason::IO_INSTRUCTION => { VmxExitReason::IO_INSTRUCTION => {
let qual = unsafe { vmread(vmcs::ro::EXIT_QUALIFICATION).unwrap() }; let qual = unsafe { vmread(vmcs::ro::EXIT_QUALIFICATION).unwrap() };
let qual = QualIo(qual); let qual_io = QualIo(qual);
io::handle_io(self, qual);
io::handle_io(self, qual_io);
self.step_next_inst().unwrap(); self.step_next_inst().unwrap();
} }
_ => { _ => {
panic!("VMExit reason: {:?}", info.get_reason()); panic!("VMExit reason: {:?}", exit_reason);
} }
} }
} }