From b3149e87fd375211aaa77c8229cf0fd4816de848 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 10 Jun 2026 09:33:48 -0700 Subject: [PATCH] hypervisor: mshv: emulate string port I/O Handle MSHV INS/OUTS port intercepts by translating the guest string operand through MshvEmulatorContext and copying data between guest memory and existing PIO callbacks. Support REP counts, zero-count REP, and direction-flag based RSI/RDI updates. Commit RIP plus RCX/RSI/RDI after the transfer completes. This removes the fw_cfg/debug-port skip. OVMF can now use the real string I/O path instead of relying on ignored ports. Signed-off-by: Wei Liu Assisted-by: Copilot:GPT-5.5 --- hypervisor/src/mshv/x86_64/io_port.rs | 250 +++++++++++++++++++++----- 1 file changed, 209 insertions(+), 41 deletions(-) diff --git a/hypervisor/src/mshv/x86_64/io_port.rs b/hypervisor/src/mshv/x86_64/io_port.rs index 0d3489fff..d5bae8e06 100644 --- a/hypervisor/src/mshv/x86_64/io_port.rs +++ b/hypervisor/src/mshv/x86_64/io_port.rs @@ -4,12 +4,24 @@ // use anyhow::anyhow; +use iced_x86::Register; use mshv_bindings::{ - HV_INTERCEPT_ACCESS_READ, HV_INTERCEPT_ACCESS_WRITE, hv_x64_io_port_intercept_message, + HV_INTERCEPT_ACCESS_READ, HV_INTERCEPT_ACCESS_WRITE, HV_X64_REGISTER_CLASS_GENERAL, + HV_X64_REGISTER_CLASS_IP, hv_register_assoc, hv_register_name_HV_X64_REGISTER_RCX, + hv_register_name_HV_X64_REGISTER_RDI, hv_register_name_HV_X64_REGISTER_RIP, + hv_register_name_HV_X64_REGISTER_RSI, hv_register_value, hv_x64_io_port_intercept_message, }; +use mshv_ioctls::set_registers_64; +use super::MshvSegmentRegister; +use crate::arch::emulator::PlatformEmulator; +use crate::arch::x86::emulator::{ + CpuStateManager, EmulatorCpuState, advance_string_op_index, string_op_backwards, + string_op_repeat_count, +}; use crate::cpu; use crate::mshv::MshvVcpu; +use crate::mshv::emulator::MshvEmulatorContext; impl MshvVcpu { #[cfg(target_arch = "x86_64")] @@ -56,38 +68,17 @@ impl MshvVcpu { &self, info: &hv_x64_io_port_intercept_message, ) -> cpu::Result<()> { - let port = info.port_number; - - /* - * XXX: Ignore QEMU fw_cfg (0x5xx) and debug console (0x402) ports. - * - * Cloud Hypervisor doesn't support fw_cfg at the moment. It does support 0x402 - * under the "fwdebug" feature flag. But that feature is not enabled by default - * and is considered legacy. - * - * OVMF unconditionally pokes these IO ports with string IO. - * - * Instead of trying to implement string IO support now which does not do much - * now, skip those ports explicitly to avoid panicking. - * - * Proper string IO support can be added once we gain the ability to translate - * guest virtual addresses to guest physical addresses on MSHV. - */ - match port { - 0x402 | 0x510 | 0x511 | 0x514 => { - self.advance_rip_update_rax(info, info.rax)?; - return Ok(()); + if Self::io_port_is_string(info) { + self.handle_string_io_port_intercept(info) + } else { + if Self::io_port_has_rep(info) { + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "REP prefix without string I/O is not supported" + ))); } - _ => {} + + self.handle_scalar_io_port_intercept(info) } - - assert!( - !Self::io_port_is_string(info), - "String IN/OUT not supported" - ); - assert!(!Self::io_port_has_rep(info), "Rep IN/OUT not supported"); - - self.handle_scalar_io_port_intercept(info) } #[cfg(target_arch = "x86_64")] @@ -103,17 +94,9 @@ impl MshvVcpu { if is_write { let data = (info.rax as u32).to_le_bytes(); - if let Some(vm_ops) = &self.vm_ops { - vm_ops - .pio_write(port.into(), &data[0..len]) - .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; - } + self.io_port_pio_write(port, &data[0..len])?; } else { - if let Some(vm_ops) = &self.vm_ops { - vm_ops - .pio_read(port.into(), &mut data[0..len]) - .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; - } + self.io_port_pio_read(port, &mut data[0..len])?; let v = u32::from_le_bytes(data); /* Preserve high bits in EAX but clear out high bits in RAX */ @@ -124,6 +107,191 @@ impl MshvVcpu { self.advance_rip_update_rax(info, ret_rax) } + + #[cfg(target_arch = "x86_64")] + fn io_port_pio_write(&self, port: u16, data: &[u8]) -> cpu::Result<()> { + if let Some(vm_ops) = &self.vm_ops { + vm_ops + .pio_write(port.into(), data) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + } + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + fn io_port_pio_read(&self, port: u16, data: &mut [u8]) -> cpu::Result<()> { + if let Some(vm_ops) = &self.vm_ops { + vm_ops + .pio_read(port.into(), data) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + } + + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + fn handle_string_io_port_intercept( + &self, + info: &hv_x64_io_port_intercept_message, + ) -> cpu::Result<()> { + let len = Self::io_port_access_len(info)?; + let is_write = Self::io_port_is_write(info)?; + let rep_prefix = Self::io_port_has_rep(info); + let port = info.port_number; + let backwards = string_op_backwards(info.header.rflags); + let mut count = string_op_repeat_count(rep_prefix, info.rcx); + let mut rcx = info.rcx; + let mut rsi = info.rsi; + let mut rdi = info.rdi; + + let mut context = MshvEmulatorContext { + vcpu: self, + mapping: None, + }; + let state = self.io_port_string_cpu_state(&context, info)?; + + while count > 0 { + if is_write { + let data = self.io_port_read_string_operand(&mut context, &state, rsi, len)?; + self.io_port_pio_write(port, &data[..len])?; + rsi = advance_string_op_index(rsi, len, backwards); + } else { + let mut data: [u8; 4] = [0; 4]; + self.io_port_pio_read(port, &mut data[..len])?; + self.io_port_write_string_operand(&mut context, &state, rdi, &data[..len])?; + rdi = advance_string_op_index(rdi, len, backwards); + } + + if rep_prefix { + rcx = rcx.wrapping_sub(1); + } + count -= 1; + } + + self.advance_rip_update_string_io_regs( + info, + rep_prefix.then_some(rcx), + is_write.then_some(rsi), + (!is_write).then_some(rdi), + ) + } + + #[cfg(target_arch = "x86_64")] + fn io_port_string_cpu_state( + &self, + context: &MshvEmulatorContext<'_>, + info: &hv_x64_io_port_intercept_message, + ) -> cpu::Result { + let mut state = context + .cpu_state(self.vp_index as usize) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + state.regs.set_rflags(info.header.rflags); + let ds: MshvSegmentRegister = info.ds_segment.into(); + let es: MshvSegmentRegister = info.es_segment.into(); + state.sregs.ds = ds.into(); + state.sregs.es = es.into(); + + Ok(state) + } + + #[cfg(target_arch = "x86_64")] + fn io_port_read_string_operand( + &self, + context: &mut MshvEmulatorContext<'_>, + state: &EmulatorCpuState, + rsi: u64, + len: usize, + ) -> cpu::Result<[u8; 4]> { + let gva = state + .linearize(Register::DS, rsi, false) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + let mut data: [u8; 4] = [0; 4]; + context + .read_memory(gva, &mut data[..len]) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + + Ok(data) + } + + #[cfg(target_arch = "x86_64")] + fn io_port_write_string_operand( + &self, + context: &mut MshvEmulatorContext<'_>, + state: &EmulatorCpuState, + rdi: u64, + data: &[u8], + ) -> cpu::Result<()> { + let gva = state + .linearize(Register::ES, rdi, true) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + context + .write_memory(gva, data) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into())) + } + + #[cfg(target_arch = "x86_64")] + fn advance_rip_update_string_io_regs( + &self, + info: &hv_x64_io_port_intercept_message, + rcx: Option, + rsi: Option, + rdi: Option, + ) -> cpu::Result<()> { + let rip = info.header.rip + info.header.instruction_length() as u64; + + if let Some(reg_page) = self.fd.get_vp_reg_page() { + let vp_reg_page = reg_page.0; + // SAFETY: access raw pointer to reg page, access union fields + unsafe { + if let Some(rcx) = rcx { + (*vp_reg_page) + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .rcx = rcx; + } + if let Some(rsi) = rsi { + (*vp_reg_page) + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .rsi = rsi; + } + if let Some(rdi) = rdi { + (*vp_reg_page) + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .rdi = rdi; + } + (*vp_reg_page).__bindgen_anon_1.__bindgen_anon_1.rip = rip; + (*vp_reg_page).dirty |= 1 << HV_X64_REGISTER_CLASS_IP; + if rcx.is_some() || rsi.is_some() || rdi.is_some() { + (*vp_reg_page).dirty |= 1 << HV_X64_REGISTER_CLASS_GENERAL; + } + } + } else { + let mut regs = vec![(hv_register_name_HV_X64_REGISTER_RIP, rip)]; + if let Some(rcx) = rcx { + regs.push((hv_register_name_HV_X64_REGISTER_RCX, rcx)); + } + if let Some(rsi) = rsi { + regs.push((hv_register_name_HV_X64_REGISTER_RSI, rsi)); + } + if let Some(rdi) = rdi { + regs.push((hv_register_name_HV_X64_REGISTER_RDI, rdi)); + } + + set_registers_64!(self.fd, regs) + .map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?; + } + + Ok(()) + } } #[cfg(test)]