// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE-BSD-3-Clause file. // // Copyright © 2019 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // extern crate arch; extern crate devices; extern crate epoll; extern crate kvm_ioctls; extern crate libc; extern crate linux_loader; extern crate net_util; extern crate signal_hook; #[cfg(feature = "pci_support")] extern crate vfio; extern crate vm_allocator; extern crate vm_memory; extern crate vm_virtio; use crate::config::VmConfig; use crate::cpu; use crate::device_manager::{get_win_size, Console, DeviceManager, DeviceManagerError}; use arch::RegionType; use devices::{ioapic, HotPlugNotificationType}; use kvm_bindings::{ kvm_enable_cap, kvm_pit_config, kvm_userspace_memory_region, KVM_CAP_SPLIT_IRQCHIP, KVM_PIT_SPEAKER_DUMMY, }; use kvm_ioctls::*; use linux_loader::cmdline::Cmdline; use linux_loader::loader::KernelLoader; use signal_hook::{iterator::Signals, SIGINT, SIGTERM, SIGWINCH}; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::io; use std::ops::Deref; use std::os::unix::io::FromRawFd; use std::sync::{Arc, Mutex, RwLock}; use std::{result, str, thread}; use vm_allocator::{GsiApic, SystemAllocator}; use vm_memory::guest_memory::FileOffset; use vm_memory::{ Address, Bytes, Error as MmapError, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestUsize, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::terminal::Terminal; const X86_64_IRQ_BASE: u32 = 6; // CPUID feature bits const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit. const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit. // 64 bit direct boot entry offset for bzImage const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200; /// Errors associated with VM management #[derive(Debug)] pub enum Error { /// Cannot open the VM file descriptor. VmFd(io::Error), /// Cannot create the KVM instance VmCreate(kvm_ioctls::Error), /// Cannot set the VM up VmSetup(kvm_ioctls::Error), /// Cannot open the kernel image KernelFile(io::Error), /// Mmap backed guest memory error GuestMemory(MmapError), /// Cannot load the kernel in memory KernelLoad(linux_loader::loader::Error), /// Cannot load the command line in memory CmdLine, PoisonedState, /// Cannot create a device manager. DeviceManager(DeviceManagerError), /// Write to the console failed. Console(vmm_sys_util::errno::Error), /// Cannot setup terminal in raw mode. SetTerminalRaw(vmm_sys_util::errno::Error), /// Cannot setup terminal in canonical mode. SetTerminalCanon(vmm_sys_util::errno::Error), /// Cannot create the system allocator CreateSystemAllocator, /// Failed parsing network parameters ParseNetworkParameters, /// Memory is overflow MemOverflow, /// Failed to create shared file. SharedFileCreate(io::Error), /// Failed to set shared file length. SharedFileSetLen(io::Error), /// Failed to allocate a memory range. MemoryRangeAllocation, /// Failed to allocate the IOAPIC memory range. IoapicRangeAllocation, /// Cannot spawn a signal handler thread SignalHandlerSpawn(io::Error), /// Failed to join on vCPU threads ThreadCleanup, /// Failed to create a new KVM instance KvmNew(kvm_ioctls::Error), /// VM is not created VmNotCreated, /// VM is not running VmNotRunning, /// Cannot clone EventFd. EventFdClone(io::Error), /// Invalid VM state transition InvalidStateTransition(VmState, VmState), /// Error from CPU handling CpuManager(cpu::Error), } pub type Result = result::Result; pub struct VmInfo<'a> { pub memory: &'a Arc>, pub vm_fd: &'a Arc, pub vm_cfg: &'a VmConfig, } #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] pub enum VmState { Created, Running, Shutdown, Paused, } impl VmState { fn valid_transition(self, new_state: VmState) -> Result<()> { match self { VmState::Created => match new_state { VmState::Created | VmState::Shutdown | VmState::Paused => { Err(Error::InvalidStateTransition(self, new_state)) } VmState::Running => Ok(()), }, VmState::Running => match new_state { VmState::Created | VmState::Running => { Err(Error::InvalidStateTransition(self, new_state)) } VmState::Paused | VmState::Shutdown => Ok(()), }, VmState::Shutdown => match new_state { VmState::Paused | VmState::Created | VmState::Shutdown => { Err(Error::InvalidStateTransition(self, new_state)) } VmState::Running => Ok(()), }, VmState::Paused => match new_state { VmState::Created | VmState::Paused => { Err(Error::InvalidStateTransition(self, new_state)) } VmState::Running | VmState::Shutdown => Ok(()), }, } } } pub struct Vm { kernel: File, memory: Arc>, threads: Vec>, devices: DeviceManager, config: Arc, on_tty: bool, signals: Option, state: RwLock, cpu_manager: Arc>, } fn get_host_cpu_phys_bits() -> u8 { use core::arch::x86_64; unsafe { let leaf = x86_64::__cpuid(0x8000_0000); // Detect and handle AMD SME (Secure Memory Encryption) properly. // Some physical address bits may become reserved when the feature is enabled. // See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1 let reduced = if leaf.eax >= 0x8000_001f && leaf.ebx == 0x6874_7541 // Vendor ID: AuthenticAMD && leaf.ecx == 0x444d_4163 && leaf.edx == 0x6974_6e65 && x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0 { (x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f } else { 0 }; if leaf.eax >= 0x8000_0008 { let leaf = x86_64::__cpuid(0x8000_0008); ((leaf.eax & 0xff) - reduced) as u8 } else { 36 } } } impl Vm { pub fn new(config: Arc, exit_evt: EventFd, reset_evt: EventFd) -> Result { let kvm = Kvm::new().map_err(Error::KvmNew)?; let kernel = File::open(&config.kernel.as_ref().unwrap().path).map_err(Error::KernelFile)?; let fd = kvm.create_vm().map_err(Error::VmCreate)?; let fd = Arc::new(fd); // Init guest memory let arch_mem_regions = arch::arch_memory_regions(config.memory.size); let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions .iter() .filter(|r| r.2 == RegionType::Ram) .map(|r| (r.0, r.1)) .collect(); let sub_regions: Vec<(GuestAddress, usize)> = arch_mem_regions .iter() .filter(|r| r.2 == RegionType::SubRegion) .map(|r| (r.0, r.1)) .collect(); // Check the number of reserved regions, and only take the first one // that's acrtually a 32-bit hole. let mut mem_hole = (GuestAddress(0), 0); for region in sub_regions.iter() { if region.0.unchecked_add(region.1 as u64).raw_value() <= 0x1_0000_0000 { mem_hole = (region.0, region.1); break; } } let guest_memory = match config.memory.file { Some(ref file) => { let mut mem_regions = Vec::<(GuestAddress, usize, Option)>::new(); for region in ram_regions.iter() { if file.is_file() { let file = OpenOptions::new() .read(true) .write(true) .open(file) .map_err(Error::SharedFileCreate)?; file.set_len(region.1 as u64) .map_err(Error::SharedFileSetLen)?; mem_regions.push((region.0, region.1, Some(FileOffset::new(file, 0)))); } else if file.is_dir() { let fs_str = format!("{}{}", file.display(), "/tmpfile_XXXXXX"); let fs = std::ffi::CString::new(fs_str).unwrap(); let mut path = fs.as_bytes_with_nul().to_owned(); let path_ptr = path.as_mut_ptr() as *mut _; let fd = unsafe { libc::mkstemp(path_ptr) }; unsafe { libc::unlink(path_ptr) }; let f = unsafe { File::from_raw_fd(fd) }; f.set_len(region.1 as u64) .map_err(Error::SharedFileSetLen)?; mem_regions.push((region.0, region.1, Some(FileOffset::new(f, 0)))); } } GuestMemoryMmap::with_files(&mem_regions).map_err(Error::GuestMemory)? } None => GuestMemoryMmap::new(&ram_regions).map_err(Error::GuestMemory)?, }; guest_memory .with_regions(|index, region| { let mem_region = kvm_userspace_memory_region { slot: index as u32, guest_phys_addr: region.start_addr().raw_value(), memory_size: region.len() as u64, userspace_addr: region.as_ptr() as u64, flags: 0, }; // Safe because the guest regions are guaranteed not to overlap. unsafe { fd.set_user_memory_region(mem_region) .map_err(|e| io::Error::from_raw_os_error(e.errno())) }?; // Mark the pages as mergeable if explicitly asked for. if config.memory.mergeable { // Safe because the address and size are valid since the // mmap succeeded. let ret = unsafe { libc::madvise( region.as_ptr() as *mut libc::c_void, region.len() as libc::size_t, libc::MADV_MERGEABLE, ) }; if ret != 0 { let err = io::Error::last_os_error(); // Safe to unwrap because the error is constructed with // last_os_error(), which ensures the output will be Some(). let errno = err.raw_os_error().unwrap(); if errno == libc::EINVAL { warn!("kernel not configured with CONFIG_KSM"); } else { warn!("madvise error: {}", err); } warn!("failed to mark pages as mergeable"); } } Ok(()) }) .map_err(|_: io::Error| Error::GuestMemory(MmapError::NoMemoryRegion))?; // Set TSS fd.set_tss_address(arch::x86_64::layout::KVM_TSS_ADDRESS.raw_value() as usize) .map_err(Error::VmSetup)?; let msi_capable = kvm.check_extension(Cap::SignalMsi); let mut cpuid_patches = Vec::new(); let mut userspace_ioapic = false; if kvm.check_extension(Cap::TscDeadlineTimer) { if kvm.check_extension(Cap::SplitIrqchip) && msi_capable { // Create split irqchip // Only the local APIC is emulated in kernel, both PICs and IOAPIC // are not. let mut cap: kvm_enable_cap = Default::default(); cap.cap = KVM_CAP_SPLIT_IRQCHIP; cap.args[0] = ioapic::NUM_IOAPIC_PINS as u64; fd.enable_cap(&cap).map_err(Error::VmSetup)?; // Because of the split irqchip, we need a userspace IOAPIC. userspace_ioapic = true; } else { // Create irqchip // A local APIC, 2 PICs and an IOAPIC are emulated in kernel. fd.create_irq_chip().map_err(Error::VmSetup)?; } // Patch tsc deadline timer bit cpuid_patches.push(cpu::CpuidPatch { function: 1, index: 0, flags_bit: None, eax_bit: None, ebx_bit: None, ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT), edx_bit: None, }); } else { // Create irqchip // A local APIC, 2 PICs and an IOAPIC are emulated in kernel. fd.create_irq_chip().map_err(Error::VmSetup)?; // Creates an in-kernel device model for the PIT. let mut pit_config = kvm_pit_config::default(); // We need to enable the emulation of a dummy speaker port stub so that writing to port 0x61 // (i.e. KVM_SPEAKER_BASE_ADDRESS) does not trigger an exit to user space. pit_config.flags = KVM_PIT_SPEAKER_DUMMY; fd.create_pit2(pit_config).map_err(Error::VmSetup)?; } // Patch hypervisor bit cpuid_patches.push(cpu::CpuidPatch { function: 1, index: 0, flags_bit: None, eax_bit: None, ebx_bit: None, ecx_bit: Some(HYPERVISOR_ECX_BIT), edx_bit: None, }); // Supported CPUID let mut cpuid = kvm .get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES) .map_err(Error::VmSetup)?; cpu::CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); let ioapic = GsiApic::new( X86_64_IRQ_BASE, ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE, ); // Let's allocate 64 GiB of addressable MMIO space, starting at 0. let mut allocator = SystemAllocator::new( GuestAddress(0), 1 << 16 as GuestUsize, GuestAddress(0), 1 << get_host_cpu_phys_bits(), mem_hole.0, mem_hole.1 as GuestUsize, vec![ioapic], ) .ok_or(Error::CreateSystemAllocator)?; // Allocate RAM and Reserved address ranges. for region in arch_mem_regions.iter() { allocator .allocate_mmio_addresses(Some(region.0), region.1 as GuestUsize, None) .ok_or(Error::MemoryRangeAllocation)?; } // Convert the guest memory into an Arc. The point being able to use it // anywhere in the code, no matter which thread might use it. // Add the RwLock aspect to guest memory as we might want to perform // additions to the memory during runtime. let guest_memory = Arc::new(RwLock::new(guest_memory)); let vm_info = VmInfo { memory: &guest_memory, vm_fd: &fd, vm_cfg: &config, }; let device_manager = DeviceManager::new( &vm_info, allocator, msi_capable, userspace_ioapic, ram_regions.len() as u32, &exit_evt, &reset_evt, ) .map_err(Error::DeviceManager)?; let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0; let boot_vcpus = config.cpus.boot_vcpus; let max_vcpus = config.cpus.max_vcpus; let cpu_manager = cpu::CpuManager::new( boot_vcpus, max_vcpus, &device_manager, guest_memory.clone(), fd, cpuid, reset_evt, ) .map_err(Error::CpuManager)?; Ok(Vm { kernel, memory: guest_memory, devices: device_manager, config, on_tty, threads: Vec::with_capacity(1), signals: None, state: RwLock::new(VmState::Created), cpu_manager, }) } fn load_kernel(&mut self) -> Result { let mut cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE); cmdline .insert_str(self.config.cmdline.args.clone()) .map_err(|_| Error::CmdLine)?; for entry in self.devices.cmdline_additions() { cmdline.insert_str(entry).map_err(|_| Error::CmdLine)?; } let cmdline_cstring = CString::new(cmdline).map_err(|_| Error::CmdLine)?; let mem = self.memory.read().unwrap(); let entry_addr = match linux_loader::loader::Elf::load( mem.deref(), None, &mut self.kernel, Some(arch::layout::HIGH_RAM_START), ) { Ok(entry_addr) => entry_addr, Err(linux_loader::loader::Error::InvalidElfMagicNumber) => { linux_loader::loader::BzImage::load( mem.deref(), None, &mut self.kernel, Some(arch::layout::HIGH_RAM_START), ) .map_err(Error::KernelLoad)? } _ => panic!("Invalid elf file"), }; linux_loader::loader::load_cmdline( mem.deref(), arch::layout::CMDLINE_START, &cmdline_cstring, ) .map_err(|_| Error::CmdLine)?; let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus(); let _max_vcpus = self.cpu_manager.lock().unwrap().max_vcpus(); #[allow(unused_mut, unused_assignments)] let mut rsdp_addr: Option = None; #[cfg(feature = "acpi")] { rsdp_addr = Some({ let end_of_range = GuestAddress((1 << get_host_cpu_phys_bits()) - 1); let mem_end = mem.end_addr(); let start_of_device_area = if mem_end < arch::layout::MEM_32BIT_RESERVED_START { arch::layout::RAM_64BIT_START } else { mem_end.unchecked_add(1) }; use crate::config::ConsoleOutputMode; crate::acpi::create_acpi_tables( &mem, boot_vcpus, _max_vcpus, self.config.serial.mode != ConsoleOutputMode::Off, start_of_device_area, end_of_range, self.devices.virt_iommu(), ) }); } match entry_addr.setup_header { Some(hdr) => { arch::configure_system( &mem, arch::layout::CMDLINE_START, cmdline_cstring.to_bytes().len() + 1, boot_vcpus, Some(hdr), rsdp_addr, ) .map_err(|_| Error::CmdLine)?; let load_addr = entry_addr .kernel_load .raw_value() .checked_add(KERNEL_64BIT_ENTRY_OFFSET) .ok_or(Error::MemOverflow)?; Ok(GuestAddress(load_addr)) } None => { arch::configure_system( &mem, arch::layout::CMDLINE_START, cmdline_cstring.to_bytes().len() + 1, boot_vcpus, None, rsdp_addr, ) .map_err(|_| Error::CmdLine)?; Ok(entry_addr.kernel_load) } } } pub fn shutdown(&mut self) -> Result<()> { let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; let new_state = VmState::Shutdown; state.valid_transition(new_state)?; if self.on_tty { // Don't forget to set the terminal in canonical mode // before to exit. io::stdin() .lock() .set_canon_mode() .map_err(Error::SetTerminalCanon)?; } // Trigger the termination of the signal_handler thread if let Some(signals) = self.signals.take() { signals.close(); } self.cpu_manager .lock() .unwrap() .shutdown() .map_err(Error::CpuManager)?; // Wait for all the threads to finish for thread in self.threads.drain(..) { thread.join().map_err(|_| Error::ThreadCleanup)? } *state = new_state; Ok(()) } pub fn pause(&mut self) -> Result<()> { let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; let new_state = VmState::Paused; state.valid_transition(new_state)?; self.cpu_manager .lock() .unwrap() .pause() .map_err(Error::CpuManager)?; *state = new_state; Ok(()) } pub fn resume(&mut self) -> Result<()> { let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; let new_state = VmState::Running; state.valid_transition(new_state)?; self.cpu_manager .lock() .unwrap() .resume() .map_err(Error::CpuManager)?; // And we're back to the Running state. *state = new_state; Ok(()) } pub fn resize(&mut self, desired_vcpus: u8) -> Result<()> { self.cpu_manager .lock() .unwrap() .resize(desired_vcpus) .map_err(Error::CpuManager)?; self.devices .notify_hotplug(HotPlugNotificationType::CPUDevicesChanged) .map_err(Error::DeviceManager)?; Ok(()) } fn os_signal_handler(signals: Signals, console_input_clone: Arc, on_tty: bool) { for signal in signals.forever() { match signal { SIGWINCH => { let (col, row) = get_win_size(); console_input_clone.update_console_size(col, row); } SIGTERM | SIGINT => { if on_tty { io::stdin() .lock() .set_canon_mode() .expect("failed to restore terminal mode"); } std::process::exit((signal != SIGTERM) as i32); } _ => (), } } } pub fn boot(&mut self) -> Result<()> { let current_state = self.get_state()?; if current_state == VmState::Paused { return self.resume(); } let new_state = VmState::Running; current_state.valid_transition(new_state)?; let entry_addr = self.load_kernel()?; self.cpu_manager .lock() .unwrap() .start_boot_vcpus(entry_addr) .map_err(Error::CpuManager)?; if self.devices.console().input_enabled() { let console = self.devices.console().clone(); let signals = Signals::new(&[SIGWINCH, SIGINT, SIGTERM]); match signals { Ok(signals) => { self.signals = Some(signals.clone()); let on_tty = self.on_tty; self.threads.push( thread::Builder::new() .name("signal_handler".to_string()) .spawn(move || Vm::os_signal_handler(signals, console, on_tty)) .map_err(Error::SignalHandlerSpawn)?, ); } Err(e) => error!("Signal not found {}", e), } if self.on_tty { io::stdin() .lock() .set_raw_mode() .map_err(Error::SetTerminalRaw)?; } } let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?; *state = new_state; Ok(()) } /// Gets an Arc to the guest memory owned by this VM. pub fn get_memory(&self) -> Arc> { self.memory.clone() } pub fn handle_stdin(&self) -> Result<()> { let mut out = [0u8; 64]; let count = io::stdin() .lock() .read_raw(&mut out) .map_err(Error::Console)?; if self.devices.console().input_enabled() { self.devices .console() .queue_input_bytes(&out[..count]) .map_err(Error::Console)?; } Ok(()) } /// Gets a thread-safe reference counted pointer to the VM configuration. pub fn get_config(&self) -> Arc { Arc::clone(&self.config) } /// Get the VM state. Returns an error if the state is poisoned. pub fn get_state(&self) -> Result { self.state .try_read() .map_err(|_| Error::PoisonedState) .map(|state| *state) } } #[cfg(test)] mod tests { use super::*; fn test_vm_state_transitions(state: VmState) { match state { VmState::Created => { // Check the transitions from Created assert!(state.valid_transition(VmState::Created).is_err()); assert!(state.valid_transition(VmState::Running).is_ok()); assert!(state.valid_transition(VmState::Shutdown).is_err()); assert!(state.valid_transition(VmState::Paused).is_err()); } VmState::Running => { // Check the transitions from Running assert!(state.valid_transition(VmState::Created).is_err()); assert!(state.valid_transition(VmState::Running).is_err()); assert!(state.valid_transition(VmState::Shutdown).is_ok()); assert!(state.valid_transition(VmState::Paused).is_ok()); } VmState::Shutdown => { // Check the transitions from Shutdown assert!(state.valid_transition(VmState::Created).is_err()); assert!(state.valid_transition(VmState::Running).is_ok()); assert!(state.valid_transition(VmState::Shutdown).is_err()); assert!(state.valid_transition(VmState::Paused).is_err()); } VmState::Paused => { // Check the transitions from Paused assert!(state.valid_transition(VmState::Created).is_err()); assert!(state.valid_transition(VmState::Running).is_ok()); assert!(state.valid_transition(VmState::Shutdown).is_ok()); assert!(state.valid_transition(VmState::Paused).is_err()); } _ => {} } } #[test] fn test_vm_created_transitions() { test_vm_state_transitions(VmState::Created); } #[test] fn test_vm_running_transitions() { test_vm_state_transitions(VmState::Running); } #[test] fn test_vm_shutdown_transitions() { test_vm_state_transitions(VmState::Shutdown); } #[test] fn test_vm_paused_transitions() { test_vm_state_transitions(VmState::Paused); } } #[allow(unused)] pub fn test_vm() { // This example based on https://lwn.net/Articles/658511/ let code = [ 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ 0x00, 0xd8, /* add %bl, %al */ 0x04, b'0', /* add $'0', %al */ 0xee, /* out %al, (%dx) */ 0xb0, b'\n', /* mov $'\n', %al */ 0xee, /* out %al, (%dx) */ 0xf4, /* hlt */ ]; let mem_size = 0x1000; let load_addr = GuestAddress(0x1000); let mem = GuestMemoryMmap::new(&[(load_addr, mem_size)]).unwrap(); let kvm = Kvm::new().expect("new KVM instance creation failed"); let vm_fd = kvm.create_vm().expect("new VM fd creation failed"); mem.with_regions(|index, region| { let mem_region = kvm_userspace_memory_region { slot: index as u32, guest_phys_addr: region.start_addr().raw_value(), memory_size: region.len() as u64, userspace_addr: region.as_ptr() as u64, flags: 0, }; // Safe because the guest regions are guaranteed not to overlap. unsafe { vm_fd.set_user_memory_region(mem_region) } }) .expect("Cannot configure guest memory"); mem.write_slice(&code, load_addr) .expect("Writing code to memory failed"); let vcpu_fd = vm_fd.create_vcpu(0).expect("new VcpuFd failed"); let mut vcpu_sregs = vcpu_fd.get_sregs().expect("get sregs failed"); vcpu_sregs.cs.base = 0; vcpu_sregs.cs.selector = 0; vcpu_fd.set_sregs(&vcpu_sregs).expect("set sregs failed"); let mut vcpu_regs = vcpu_fd.get_regs().expect("get regs failed"); vcpu_regs.rip = 0x1000; vcpu_regs.rax = 2; vcpu_regs.rbx = 3; vcpu_regs.rflags = 2; vcpu_fd.set_regs(&vcpu_regs).expect("set regs failed"); loop { match vcpu_fd.run().expect("run failed") { VcpuExit::IoIn(addr, data) => { println!( "IO in -- addr: {:#x} data [{:?}]", addr, str::from_utf8(&data).unwrap() ); } VcpuExit::IoOut(addr, data) => { println!( "IO out -- addr: {:#x} data [{:?}]", addr, str::from_utf8(&data).unwrap() ); } VcpuExit::MmioRead(_addr, _data) => {} VcpuExit::MmioWrite(_addr, _data) => {} VcpuExit::Unknown => {} VcpuExit::Exception => {} VcpuExit::Hypercall => {} VcpuExit::Debug => {} VcpuExit::Hlt => { println!("HLT"); } VcpuExit::IrqWindowOpen => {} VcpuExit::Shutdown => {} VcpuExit::FailEntry => {} VcpuExit::Intr => {} VcpuExit::SetTpr => {} VcpuExit::TprAccess => {} VcpuExit::S390Sieic => {} VcpuExit::S390Reset => {} VcpuExit::Dcr => {} VcpuExit::Nmi => {} VcpuExit::InternalError => {} VcpuExit::Osi => {} VcpuExit::PaprHcall => {} VcpuExit::S390Ucontrol => {} VcpuExit::Watchdog => {} VcpuExit::S390Tsch => {} VcpuExit::Epr => {} VcpuExit::SystemEvent => {} VcpuExit::S390Stsi => {} VcpuExit::IoapicEoi(_vector) => {} VcpuExit::Hyperv => {} } // r => panic!("unexpected exit reason: {:?}", r), } }