mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
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 <liuwe@microsoft.com> Assisted-by: Copilot:GPT-5.5
This commit is contained in:
@@ -4,12 +4,24 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use iced_x86::Register;
|
||||||
use mshv_bindings::{
|
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::cpu;
|
||||||
use crate::mshv::MshvVcpu;
|
use crate::mshv::MshvVcpu;
|
||||||
|
use crate::mshv::emulator::MshvEmulatorContext;
|
||||||
|
|
||||||
impl MshvVcpu {
|
impl MshvVcpu {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -56,38 +68,17 @@ impl MshvVcpu {
|
|||||||
&self,
|
&self,
|
||||||
info: &hv_x64_io_port_intercept_message,
|
info: &hv_x64_io_port_intercept_message,
|
||||||
) -> cpu::Result<()> {
|
) -> cpu::Result<()> {
|
||||||
let port = info.port_number;
|
if Self::io_port_is_string(info) {
|
||||||
|
self.handle_string_io_port_intercept(info)
|
||||||
/*
|
} else {
|
||||||
* XXX: Ignore QEMU fw_cfg (0x5xx) and debug console (0x402) ports.
|
if Self::io_port_has_rep(info) {
|
||||||
*
|
return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||||
* Cloud Hypervisor doesn't support fw_cfg at the moment. It does support 0x402
|
"REP prefix without string I/O is not supported"
|
||||||
* 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(());
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
|
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")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -103,17 +94,9 @@ impl MshvVcpu {
|
|||||||
|
|
||||||
if is_write {
|
if is_write {
|
||||||
let data = (info.rax as u32).to_le_bytes();
|
let data = (info.rax as u32).to_le_bytes();
|
||||||
if let Some(vm_ops) = &self.vm_ops {
|
self.io_port_pio_write(port, &data[0..len])?;
|
||||||
vm_ops
|
|
||||||
.pio_write(port.into(), &data[0..len])
|
|
||||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if let Some(vm_ops) = &self.vm_ops {
|
self.io_port_pio_read(port, &mut data[0..len])?;
|
||||||
vm_ops
|
|
||||||
.pio_read(port.into(), &mut data[0..len])
|
|
||||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let v = u32::from_le_bytes(data);
|
let v = u32::from_le_bytes(data);
|
||||||
/* Preserve high bits in EAX but clear out high bits in RAX */
|
/* 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)
|
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<EmulatorCpuState> {
|
||||||
|
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<u64>,
|
||||||
|
rsi: Option<u64>,
|
||||||
|
rdi: Option<u64>,
|
||||||
|
) -> 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)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user