├── .gitignore ├── Cargo.toml ├── LICENSE-BSD-3-Clause ├── README.md ├── examples ├── demo.rs └── tui.rs ├── src ├── include │ ├── mod.rs │ ├── specialreg.rs │ ├── vmm.rs │ └── vmm_dev.rs ├── lib.rs ├── system.rs └── vm.rs └── tests ├── api.rs └── registers.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bhyve-api" 3 | version = "0.0.1" 4 | authors = ["Allison Randal "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libc = "*" 9 | vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" } 10 | -------------------------------------------------------------------------------- /LICENSE-BSD-3-Clause: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Oxide Computer Company 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of the copyright holder nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bhyve API 2 | 3 | A Rust library interface to bhyve's kernel ioctl interface. 4 | 5 | This library is unmaintained. In the subsequent years, illumos' bhyve 6 | kernel interface has changed. This library has not been tested or 7 | updated to match, and in both cases the interfaces to FreeBSD and 8 | illumos `bhyve` have diverged. 9 | 10 | The Rust bindings for illumos' bhyve as used in Propolis, the userland 11 | VMM maintained by Oxide, are developed alongside Propolis and housed 12 | in its repository. 13 | 14 | ## Prerequisites 15 | 16 | The main requirement is Rust, the bhyve API library has been tested on 17 | the version (1.42.0) packaged with OmniOS CE r151032. Install the 18 | package `ooce/developer/rust`. 19 | 20 | ### bhyve 21 | 22 | To run the tests or examples, you will also need bhyve installed. On 23 | OmniOS CE these packages are `system/bhyve` and 24 | `system/library/bhyve` (version 0.5.11-151032.0). 25 | 26 | Once you've installed the bhyve packages, you can check if your 27 | hardware is supported by running: 28 | 29 | ``` 30 | pfexec bhhwcompat -v 31 | ``` 32 | 33 | ## Examples 34 | 35 | There are two example scripts included in `examples/`, one simple 36 | command-line interface, and one demo that illustrates the features. 37 | Both currently require root permissions, because they create real VM 38 | devices. The demo takes no command-line arguments, and can be run as: 39 | 40 | ``` 41 | sudo cargo run --example demo 42 | ``` 43 | 44 | The command-line example does take arguments, so can be run as: 45 | 46 | ``` 47 | sudo cargo run --example tui -- create vmname 48 | sudo cargo run --example tui -- run vmname 49 | sudo cargo run --example tui -- destroy vmname 50 | ``` 51 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | // A minimal example running x86 assembly language. 2 | 3 | // This example is based on https://lwn.net/Articles/658511/. 4 | // Portions Copyright 2017 The Chromium OS Authors, and Copyright 2018 5 | // Amazon.com, Inc. 6 | 7 | extern crate bhyve_api; 8 | 9 | use bhyve_api::system::*; 10 | use bhyve_api::vm::*; 11 | 12 | use std::io::Write; 13 | use std::slice; 14 | use std::ptr::null_mut; 15 | 16 | const BSP: i32 = 0; 17 | 18 | const RTC_LMEM_LSB: i32 = 0x34; 19 | const RTC_LMEM_MSB: i32 = 0x35; 20 | 21 | const KB: usize = 1024; 22 | const MB: usize = 1024 * KB; 23 | 24 | #[allow(non_upper_case_globals)] 25 | const m_64KB: usize = 64*KB; 26 | #[allow(non_upper_case_globals)] 27 | const m_16MB: usize = 16*MB; 28 | 29 | 30 | 31 | fn main() { 32 | let vm_name = "helloworld"; 33 | let mem_size: usize = 20 * MB; 34 | let guest_addr: usize = 0xfff0; 35 | let asm_code: &[u8] = &[ 36 | 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ 37 | 0x00, 0xd8, /* add %bl, %al */ 38 | 0x04, b'0', /* add $'0', %al */ 39 | 0xee, /* out %al, %dx */ 40 | // 0xb0, 0x0a, /* mov $'\n', %al */ 41 | // 0xee, /* out %al, %dx */ 42 | 0xf4, /* hlt */ 43 | ]; 44 | 45 | 46 | let host_addr: *mut u8 = unsafe { 47 | libc::mmap( 48 | null_mut(), 49 | mem_size, 50 | libc::PROT_READ | libc::PROT_WRITE, 51 | libc::MAP_ANONYMOUS | libc::MAP_SHARED | libc::MAP_NORESERVE, 52 | -1, 53 | 0, 54 | ) as *mut u8 55 | }; 56 | 57 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 58 | println!("Opened a filehandle to /dev/vmmctl"); 59 | vmmctl.create_vm(vm_name).expect("failed to create VM device"); 60 | println!("Created a device at /dev/vmm/{}", vm_name); 61 | 62 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 63 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 64 | 65 | vm.reinit().expect("failed to re-initialize VM"); 66 | vm.set_topology(1, 1, 1).expect("failed to set CPU topology"); 67 | vm.set_x2apic_state(BSP, false).expect("failed to disable x2APIC"); 68 | vm.set_capability(BSP, vm_cap_type::VM_CAP_UNRESTRICTED_GUEST, 1).expect("unrestricted guest capability not available"); 69 | vm.set_capability(BSP, vm_cap_type::VM_CAP_HALT_EXIT, 1).expect("exit on halt guest capability not available"); 70 | 71 | vm.setup_lowmem(host_addr as u64, mem_size).expect("failed to set guest memory"); 72 | 73 | let lomem = (mem_size - m_16MB) / m_64KB; 74 | vm.rtc_write(RTC_LMEM_LSB, lomem as u8).expect("failed to set RTC memory size"); 75 | vm.rtc_write(RTC_LMEM_MSB, (lomem >> 8) as u8).expect("failed to set RTC memory size"); 76 | 77 | // Write the x86 assembly code in the guest memory. 78 | let offset: u64 = host_addr as u64 + guest_addr as u64; 79 | let slice_size = mem_size - guest_addr; 80 | unsafe { 81 | let mut slice = slice::from_raw_parts_mut(offset as *mut u8, slice_size); 82 | slice.write(&asm_code).unwrap(); 83 | } 84 | 85 | // Setup registers 86 | vm.vcpu_reset(BSP).expect("failed to set initial state of registers"); 87 | 88 | let (_base, limit, access) = vm.get_desc(BSP, vm_reg_name::VM_REG_GUEST_CS).expect("failed to get CS desc"); 89 | vm.set_desc(BSP, vm_reg_name::VM_REG_GUEST_CS, guest_addr as u64, limit, access).expect("failed to set CS desc"); 90 | 91 | vm.set_register(BSP, vm_reg_name::VM_REG_GUEST_RIP, guest_addr as u64).expect("failed to set RIP register"); 92 | println!("Setting inputs to add 2 + 3"); 93 | vm.set_register(BSP, vm_reg_name::VM_REG_GUEST_RAX, 2).expect("failed to set RAX register"); 94 | vm.set_register(BSP, vm_reg_name::VM_REG_GUEST_RBX, 3).expect("failed to set RBX register"); 95 | 96 | 97 | match vm.activate_vcpu(BSP) { 98 | Ok(_) => println!("Activated CPU 0 for VM at /dev/vmm/{}", vm_name), 99 | Err(e) => println!("Failed to activate CPU 0 for VM at /dev/vmm/{}, with error: {}", vm_name, e), 100 | }; 101 | 102 | loop { 103 | let rip = vm.get_register(BSP, vm_reg_name::VM_REG_GUEST_RIP).unwrap(); 104 | println!("RIP reg before run is {}", rip); 105 | 106 | match vm.run(BSP).expect("failed to run VM") { 107 | VmExit::IoOut(port, bytes, value) => { 108 | let data: [u8; 4] = value.to_le_bytes(); 109 | println!("exit for IoOut, port={}, bytes={}, value={}", port, bytes, value); 110 | if data[0] == 53 { 111 | println!("Got expected result, ASCII code for the number 5"); 112 | } 113 | } 114 | VmExit::IoOutStr(port, bytes, index, count, repeat) => { 115 | println!("exit for IoOutStr, port={}, bytes={}, index={}, count={}, repeat={}", port, bytes, index, count, repeat); 116 | } 117 | VmExit::Vmx(s, r, q, t, e) => { 118 | println!("exit for Vmx, source={}, reason={}, qualification={:b}, inst type={}, inst error={}", s, r, q, t, e); 119 | if r == 2 { 120 | println!("Exit reason is triple fault"); 121 | break; 122 | } 123 | } 124 | VmExit::Bogus => { 125 | println!("exit for Bogus"); 126 | break; 127 | } 128 | VmExit::Halt => { 129 | println!("exit for Halt"); 130 | break; 131 | } 132 | VmExit::Suspended => { 133 | println!("exit for Suspended"); 134 | break; 135 | } 136 | reason => println!("Unhandled exit reason {:?}", reason) 137 | } 138 | } 139 | 140 | 141 | vmmctl.destroy_vm(vm_name).expect("failed to destroy VM"); 142 | println!("Destroyed a device at /dev/vmm/{}", vm_name); 143 | } 144 | -------------------------------------------------------------------------------- /examples/tui.rs: -------------------------------------------------------------------------------- 1 | // A simplistic text user interface (command-line interface) for the library. 2 | // Avoiding dev dependencies on external crates like Clap, so the argument 3 | // handling is primitive. 4 | 5 | extern crate bhyve_api; 6 | 7 | use std::env; 8 | use bhyve_api::system::*; 9 | use bhyve_api::vm::*; 10 | 11 | const BSP: i32 = 0; 12 | 13 | fn main() { 14 | let args: Vec = env::args().collect(); 15 | if "create" == &args[1] { 16 | cmd_create(&args[2]); 17 | } else if "destroy" == &args[1] { 18 | cmd_destroy(&args[2]); 19 | } else if "run" == &args[1] { 20 | cmd_run_vm(&args[2]); 21 | } else if "stats" == &args[1] { 22 | cmd_stats_vm(&args[2]); 23 | } else if "topology" == &args[1] { 24 | cmd_vcpu_top(&args[2]); 25 | } else if "activate" == &args[1] { 26 | cmd_vcpu_activate(&args[2]); 27 | } else if "suspend" == &args[1] { 28 | cmd_vcpu_suspend(&args[2]); 29 | } else if "resume" == &args[1] { 30 | cmd_vcpu_resume(&args[2]); 31 | } 32 | } 33 | 34 | fn cmd_create(vm_name: &str) { 35 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 36 | match vmmctl.create_vm(vm_name) { 37 | Ok(_) => println!("Created a device at /dev/vmm/{}", vm_name), 38 | Err(e) => println!("Unable to create device at /dev/vmm/{}, with error: {}", vm_name, e), 39 | }; 40 | } 41 | 42 | fn cmd_destroy(vm_name: &str) { 43 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 44 | match vmmctl.destroy_vm(vm_name) { 45 | Ok(_) => println!("Destroyed a device at /dev/vmm/{}", vm_name), 46 | Err(e) => println!("Unable to destroy device at /dev/vmm/{}, with error: {}", vm_name, e), 47 | }; 48 | } 49 | 50 | fn cmd_run_vm(vm_name: &str) { 51 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 52 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 53 | 54 | match vm.set_topology(1, 1, 1) { 55 | Ok(_) => println!("Set CPU topology for VM at /dev/vmm/{}", vm_name), 56 | Err(e) => println!("Failed to set CPU topology for VM at /dev/vmm/{}, with error: {}", vm_name, e), 57 | }; 58 | 59 | let (sockets, cores, threads, maxcpus) = vm.get_topology().expect("failed to get CPU topology for VM"); 60 | println!("CPU topology current values: sockets={}, cores={}, threads={}, maxcpus={}", sockets, cores, threads, maxcpus); 61 | 62 | vm.set_x2apic_state(BSP, false).expect("failed to disable x2APIC"); 63 | 64 | match vm.get_x2apic_state(BSP).expect("failed to get x2APIC state") { 65 | true => println!("x2APIC enabled"), 66 | false => println!("x2APIC disabled"), 67 | } 68 | 69 | match vm.run(0) { 70 | Ok(_) => println!("Successful run for VM at /dev/vmm/{}", vm_name), 71 | Err(e) => println!("Failed run for VM at /dev/vmm/{}, with error: {}", vm_name, e), 72 | }; 73 | } 74 | 75 | fn cmd_vcpu_top(vm_name: &str) { 76 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 77 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 78 | 79 | let (sockets, cores, threads, maxcpus) = vm.get_topology().expect("failed to get CPU topology for VM"); 80 | println!("CPU topology current values: sockets={}, cores={}, threads={}, maxcpus={}", sockets, cores, threads, maxcpus); 81 | } 82 | 83 | fn cmd_stats_vm(vm_name: &str) { 84 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 85 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 86 | 87 | match vm.get_stats(0) { 88 | Ok(entries) => println!("Got stats for VM at /dev/vmm/{}, {} entries", vm_name, entries), 89 | Err(e) => println!("Failed to get stats for VM at /dev/vmm/{}, with error: {}", vm_name, e), 90 | }; 91 | } 92 | 93 | fn cmd_vcpu_activate(vm_name: &str) { 94 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 95 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 96 | 97 | match vm.activate_vcpu(0) { 98 | Ok(_) => println!("Activated CPU 0 for VM at /dev/vmm/{}", vm_name), 99 | Err(e) => println!("Failed to activate CPU 0 for VM at /dev/vmm/{}, with error: {}", vm_name, e), 100 | }; 101 | } 102 | 103 | fn cmd_vcpu_suspend(vm_name: &str) { 104 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 105 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 106 | 107 | match vm.suspend_vcpu(0) { 108 | Ok(_) => println!("Suspended CPU 0 for VM at /dev/vmm/{}", vm_name), 109 | Err(e) => println!("Failed to suspend CPU 0 for VM at /dev/vmm/{}, with error: {}", vm_name, e), 110 | }; 111 | } 112 | 113 | fn cmd_vcpu_resume(vm_name: &str) { 114 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 115 | println!("Opened a filehandle to /dev/vmm/{}", vm.name); 116 | 117 | match vm.resume_vcpu(0) { 118 | Ok(_) => println!("Resumed CPU 0 for VM at /dev/vmm/{}", vm_name), 119 | Err(e) => println!("Failed to resume CPU 0 for VM at /dev/vmm/{}, with error: {}", vm_name, e), 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /src/include/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod vmm; 2 | pub mod vmm_dev; 3 | pub mod specialreg; 4 | -------------------------------------------------------------------------------- /src/include/specialreg.rs: -------------------------------------------------------------------------------- 1 | //! Constants for interfacing with the Bhyve ioctl interface. 2 | //! 3 | //! These are defined in Rust, but mimic the C constants defined 4 | //! in `machine/specialreg.h`. 5 | 6 | pub const CR0_NE: u64 = 0x00000020; // Numeric Error enable (EX16 vs IRQ13) 7 | -------------------------------------------------------------------------------- /src/include/vmm.rs: -------------------------------------------------------------------------------- 1 | //! Constants and structs for interfacing with the Bhyve ioctl interface. 2 | //! 3 | //! These are defined in Rust, but mimic the C constants and structs 4 | //! defined in `machine/vmm.h`. 5 | 6 | 7 | use std::os::raw::{c_int, c_uint, c_ulonglong}; 8 | 9 | pub const VM_MAXCPU: usize = 32; // maximum virtual cpus 10 | 11 | #[repr(C)] 12 | #[allow(non_camel_case_types, unused)] 13 | #[derive(Copy, Clone)] 14 | pub enum vm_suspend_how { 15 | VM_SUSPEND_NONE, 16 | VM_SUSPEND_RESET, 17 | VM_SUSPEND_POWEROFF, 18 | VM_SUSPEND_HALT, 19 | VM_SUSPEND_TRIPLEFAULT, 20 | VM_SUSPEND_LAST 21 | } 22 | 23 | // Identifiers for architecturally defined registers. 24 | #[repr(C)] 25 | #[allow(non_camel_case_types, unused)] 26 | #[derive(Copy, Clone)] 27 | pub enum vm_reg_name { 28 | VM_REG_GUEST_RAX, 29 | VM_REG_GUEST_RBX, 30 | VM_REG_GUEST_RCX, 31 | VM_REG_GUEST_RDX, 32 | VM_REG_GUEST_RSI, 33 | VM_REG_GUEST_RDI, 34 | VM_REG_GUEST_RBP, 35 | VM_REG_GUEST_R8, 36 | VM_REG_GUEST_R9, 37 | VM_REG_GUEST_R10, 38 | VM_REG_GUEST_R11, 39 | VM_REG_GUEST_R12, 40 | VM_REG_GUEST_R13, 41 | VM_REG_GUEST_R14, 42 | VM_REG_GUEST_R15, 43 | VM_REG_GUEST_CR0, 44 | VM_REG_GUEST_CR3, 45 | VM_REG_GUEST_CR4, 46 | VM_REG_GUEST_DR7, 47 | VM_REG_GUEST_RSP, 48 | VM_REG_GUEST_RIP, 49 | VM_REG_GUEST_RFLAGS, 50 | VM_REG_GUEST_ES, 51 | VM_REG_GUEST_CS, 52 | VM_REG_GUEST_SS, 53 | VM_REG_GUEST_DS, 54 | VM_REG_GUEST_FS, 55 | VM_REG_GUEST_GS, 56 | VM_REG_GUEST_LDTR, 57 | VM_REG_GUEST_TR, 58 | VM_REG_GUEST_IDTR, 59 | VM_REG_GUEST_GDTR, 60 | VM_REG_GUEST_EFER, 61 | VM_REG_GUEST_CR2, 62 | VM_REG_GUEST_PDPTE0, 63 | VM_REG_GUEST_PDPTE1, 64 | VM_REG_GUEST_PDPTE2, 65 | VM_REG_GUEST_PDPTE3, 66 | VM_REG_GUEST_INTR_SHADOW, 67 | VM_REG_GUEST_DR0, 68 | VM_REG_GUEST_DR1, 69 | VM_REG_GUEST_DR2, 70 | VM_REG_GUEST_DR3, 71 | VM_REG_GUEST_DR6, 72 | VM_REG_LAST 73 | } 74 | 75 | #[repr(C)] 76 | #[allow(non_camel_case_types, unused)] 77 | #[derive(Copy, Clone)] 78 | pub enum x2apic_state { 79 | X2APIC_DISABLED, 80 | X2APIC_ENABLED, 81 | X2APIC_STATE_LAST 82 | } 83 | 84 | // Identifiers for optional vmm capabilities 85 | #[repr(C)] 86 | #[allow(non_camel_case_types, unused)] 87 | #[derive(Copy, Clone)] 88 | pub enum vm_cap_type { 89 | VM_CAP_HALT_EXIT, 90 | VM_CAP_MTRAP_EXIT, 91 | VM_CAP_PAUSE_EXIT, 92 | VM_CAP_UNRESTRICTED_GUEST, 93 | VM_CAP_ENABLE_INVPCID, 94 | VM_CAP_MAX 95 | } 96 | 97 | 98 | // The 'access' field has the format specified in Table 21-2 of the Intel 99 | // Architecture Manual vol 3b. 100 | // 101 | // XXX The contents of the 'access' field are architecturally defined except 102 | // bit 16 - Segment Unusable. 103 | #[repr(C)] 104 | #[derive(Copy, Clone, Default)] 105 | pub struct seg_desc { 106 | pub base: c_ulonglong, 107 | pub limit: c_uint, 108 | pub access: c_uint, 109 | } 110 | 111 | #[repr(C)] 112 | #[allow(non_camel_case_types, unused)] 113 | #[derive(Copy, Clone)] 114 | pub enum vm_cpu_mode { 115 | CPU_MODE_REAL, 116 | CPU_MODE_PROTECTED, 117 | CPU_MODE_COMPATIBILITY, /* IA-32E mode (CS.L = 0) */ 118 | CPU_MODE_64BIT, /* IA-32E mode (CS.L = 1) */ 119 | } 120 | 121 | #[repr(C)] 122 | #[allow(non_camel_case_types, unused)] 123 | #[derive(Copy, Clone)] 124 | pub enum vm_paging_mode { 125 | PAGING_MODE_FLAT, 126 | PAGING_MODE_32, 127 | PAGING_MODE_PAE, 128 | PAGING_MODE_64, 129 | } 130 | 131 | #[repr(C)] 132 | #[derive(Copy, Clone)] 133 | pub struct vm_guest_paging { 134 | pub cr3: c_ulonglong, 135 | pub cpl: c_int, 136 | pub cpu_mode: vm_cpu_mode, 137 | pub paging_mode: vm_paging_mode, 138 | } 139 | 140 | // The data structures 'vie' and 'vie_op' are meant to be opaque to the 141 | // consumers of instruction decoding. The only reason why their contents 142 | // need to be exposed is because they are part of the 'vm_exit' structure. 143 | // 144 | // These structs are not public, and should never be used. They are 145 | // defined solely to allow Rust to calculate adequate memory allocation 146 | // for the 'vm_exit' struct. 147 | 148 | #[repr(C)] 149 | #[derive(Copy, Clone)] 150 | struct vie_op { 151 | op_byte: u8, 152 | op_type: u8, 153 | op_flags: u16, 154 | } 155 | 156 | // For now, this is punting on C bitfields by allocating arrays of bytes. 157 | // If we end up using this code, we will want some more precise way to 158 | // mimic C bitfields, like David Tolnay's #[bitfield] attribute macro. 159 | 160 | #[repr(C)] 161 | #[derive(Copy, Clone)] 162 | struct vie { 163 | inst: [u8; 15], 164 | num_valid: u8, 165 | num_processed: u8, 166 | bitfields: [u8; 5], // This is two bits too large 167 | disp_bytes: u8, 168 | imm_bytes: u8, 169 | scale: u8, 170 | base_register: i32, 171 | index_register: i32, 172 | segment_register: i32, 173 | displacement: i64, 174 | immediate: i64, 175 | decoded: u8, 176 | op: vie_op, 177 | } 178 | 179 | #[repr(C)] 180 | #[allow(non_camel_case_types, unused)] 181 | #[derive(Copy, Clone)] 182 | pub enum vm_exitcode { 183 | VM_EXITCODE_INOUT, 184 | VM_EXITCODE_VMX, 185 | VM_EXITCODE_BOGUS, 186 | VM_EXITCODE_RDMSR, 187 | VM_EXITCODE_WRMSR, 188 | VM_EXITCODE_HLT, 189 | VM_EXITCODE_MTRAP, 190 | VM_EXITCODE_PAUSE, 191 | VM_EXITCODE_PAGING, 192 | VM_EXITCODE_INST_EMUL, 193 | VM_EXITCODE_SPINUP_AP, 194 | VM_EXITCODE_DEPRECATED1, // used to be SPINDOWN_CPU 195 | VM_EXITCODE_RUNBLOCK, 196 | VM_EXITCODE_IOAPIC_EOI, 197 | VM_EXITCODE_SUSPENDED, 198 | VM_EXITCODE_INOUT_STR, 199 | VM_EXITCODE_TASK_SWITCH, 200 | VM_EXITCODE_MONITOR, 201 | VM_EXITCODE_MWAIT, 202 | VM_EXITCODE_SVM, 203 | VM_EXITCODE_REQIDLE, 204 | VM_EXITCODE_DEBUG, 205 | VM_EXITCODE_VMINSN, 206 | VM_EXITCODE_HT, 207 | VM_EXITCODE_MAX 208 | } 209 | 210 | #[repr(C)] 211 | #[derive(Copy, Clone)] 212 | pub struct vm_inout { 213 | bitfields: u16, 214 | pub port: u16, 215 | pub eax: u32, 216 | } 217 | 218 | impl vm_inout { 219 | pub fn bytes(&self) -> u16 { 220 | // We only care about the first three bits of the bitfield 221 | let bytes = self.bitfields & 0b0111; 222 | return bytes; 223 | } 224 | pub fn is_in(&self) -> bool { 225 | // We only care about the fourth bit of the bitfield 226 | let mask: u16 = 0b01000; 227 | if (self.bitfields & mask) == mask { 228 | return true; 229 | } else { 230 | return false; 231 | } 232 | } 233 | pub fn is_string(&self) -> bool { 234 | // We only care about the fifth bit of the bitfield 235 | let mask: u16 = 0b010000; 236 | if (self.bitfields & mask) == mask { 237 | return true; 238 | } else { 239 | return false; 240 | } 241 | } 242 | pub fn is_repeat(&self) -> bool { 243 | // We only care about the sixth bit of the bitfield 244 | let mask: u16 = 0b0100000; 245 | if (self.bitfields & mask) == mask { 246 | return true; 247 | } else { 248 | return false; 249 | } 250 | } 251 | } 252 | 253 | #[repr(C)] 254 | #[derive(Copy, Clone)] 255 | pub struct vm_inout_str { 256 | pub inout: vm_inout, // must be the first element 257 | pub paging: vm_guest_paging, 258 | pub rflags: c_ulonglong, 259 | pub cr0: c_ulonglong, 260 | pub index: c_ulonglong, 261 | pub count: c_ulonglong, // is_repeat=true (%rcx), is_repeat=false (1) 262 | pub addrsize: c_int, 263 | pub segname: vm_reg_name, 264 | pub seg_desc: seg_desc, 265 | } 266 | 267 | #[repr(C)] 268 | #[allow(non_camel_case_types, unused)] 269 | #[derive(Copy, Clone)] 270 | pub enum task_switch_reason { 271 | TSR_CALL, 272 | TSR_IRET, 273 | TSR_JMP, 274 | TSR_IDT_GATE, // task gate in IDT 275 | } 276 | 277 | #[repr(C)] 278 | #[derive(Copy, Clone)] 279 | pub struct vm_task_switch { 280 | tsssel: u16, // new TSS selector 281 | ext: c_int, // task switch due to external event 282 | errcode: c_uint, 283 | errcode_valid: c_int, // push 'errcode' on the new stack 284 | reason: task_switch_reason, 285 | paging: vm_guest_paging, 286 | } 287 | 288 | #[repr(C)] 289 | #[derive(Copy, Clone)] 290 | pub struct vm_exit { 291 | pub exitcode: vm_exitcode, 292 | pub inst_length: c_int, // 0 means unknown 293 | pub rip: c_ulonglong, 294 | pub u: vm_exit_payload, 295 | } 296 | 297 | impl Default for vm_exit { 298 | fn default() -> vm_exit { 299 | let payload = vm_exit_payload { empty: 0 }; 300 | vm_exit { 301 | exitcode: vm_exitcode::VM_EXITCODE_MAX, 302 | inst_length: 0, 303 | rip: 0, 304 | u: payload 305 | } 306 | } 307 | } 308 | 309 | #[repr(C)] 310 | #[derive(Copy, Clone)] 311 | pub union vm_exit_payload { 312 | pub inout: vm_inout, 313 | pub inout_str: vm_inout_str, 314 | pub paging: vm_exit_paging, 315 | pub inst_emul: vm_exit_inst_emul, 316 | pub vmx: vm_exit_vmx, 317 | pub svm: vm_exit_svm, 318 | pub msr: vm_exit_msr, 319 | pub spinup_ap: vm_exit_spinup_ap, 320 | pub hlt: vm_exit_hlt, 321 | pub ioapic_eoi: vm_exit_ioapic_eoi, 322 | pub suspended: vm_exit_suspended, 323 | pub task_switch: vm_task_switch, 324 | empty: c_int, 325 | } 326 | 327 | 328 | #[repr(C)] 329 | #[derive(Copy, Clone)] 330 | pub struct vm_exit_paging { 331 | pub gpa: c_ulonglong, 332 | pub fault_type: c_int, 333 | } 334 | 335 | #[repr(C)] 336 | #[derive(Copy, Clone)] 337 | pub struct vm_exit_inst_emul { 338 | pub gpa: c_ulonglong, 339 | pub gla: c_ulonglong, 340 | pub cs_base: c_ulonglong, 341 | pub cs_d: c_int, // CS.D 342 | pub paging: vm_guest_paging, 343 | vie: vie, 344 | } 345 | 346 | // VMX specific payload. Used when there is no "better" 347 | // exitcode to represent the VM-exit. 348 | #[repr(C)] 349 | #[derive(Copy, Clone)] 350 | pub struct vm_exit_vmx { 351 | pub status: c_int, // vmx inst status 352 | 353 | // 'exit_reason' and 'exit_qualification' are valid 354 | // only if 'status' is zero. 355 | pub exit_reason: c_uint, 356 | pub exit_qualification: c_ulonglong, 357 | 358 | // 'inst_error' and 'inst_type' are valid 359 | // only if 'status' is non-zero. 360 | pub inst_type: c_int, 361 | pub inst_error: c_int, 362 | } 363 | 364 | #[repr(C)] 365 | #[derive(Copy, Clone)] 366 | pub struct vm_exit_svm { 367 | pub exitcode: c_ulonglong, 368 | pub exitinfo1: c_ulonglong, 369 | pub exitinfo2: c_ulonglong, 370 | } 371 | 372 | #[repr(C)] 373 | #[derive(Copy, Clone)] 374 | pub struct vm_exit_msr { 375 | pub code: c_uint, // ecx value 376 | pub wval: c_ulonglong, 377 | } 378 | 379 | #[repr(C)] 380 | #[derive(Copy, Clone)] 381 | pub struct vm_exit_spinup_ap { 382 | pub vcpu: c_int, 383 | pub rip: c_ulonglong, 384 | } 385 | 386 | #[repr(C)] 387 | #[derive(Copy, Clone)] 388 | pub struct vm_exit_hlt { 389 | pub rflags: c_ulonglong, 390 | pub intr_status: c_ulonglong, 391 | } 392 | 393 | #[repr(C)] 394 | #[derive(Copy, Clone)] 395 | pub struct vm_exit_ioapic_eoi { 396 | pub vector: c_int, 397 | } 398 | 399 | #[repr(C)] 400 | #[derive(Copy, Clone)] 401 | pub struct vm_exit_suspended { 402 | pub how: vm_suspend_how, 403 | } 404 | -------------------------------------------------------------------------------- /src/include/vmm_dev.rs: -------------------------------------------------------------------------------- 1 | //! Constants and structs for interfacing with the Bhyve ioctl interface. 2 | //! 3 | //! These are defined in Rust, but mimic the C constants and structs 4 | //! defined in `machine/vmm_dev.h`, `sys/ioccom.h`, and `sys/time.h`. 5 | 6 | use std::os::raw::{c_int, c_uint, c_long, c_longlong, c_ulonglong, c_char}; 7 | use std::mem::size_of; 8 | use libc::{size_t}; 9 | 10 | use crate::include::vmm::*; 11 | 12 | // Define const from sys/param.h 13 | 14 | const SPECNAMELEN: usize = 255; // max length of devicename 15 | 16 | // Define struct from sys/time.h 17 | 18 | #[repr(C)] 19 | #[derive(Copy, Clone, Default)] 20 | pub struct timeval { 21 | pub tv_sec: c_long, // seconds 22 | pub tv_usec: c_long, // and microseconds 23 | } 24 | 25 | // Define constants from sys/ioccom.h 26 | 27 | // Ioctl's have the command encoded in the lower word, and the size of 28 | // any in or out parameters in the upper word. The high 3 bits of the 29 | // upper word are used to encode the in/out status of the parameter. 30 | 31 | const IOCPARM_MASK: c_uint = 0xff; // parameters must be < 256 bytes 32 | const IOCPARM_SIZESHIFT: c_uint = 16; 33 | 34 | const IOC_VOID: c_uint = 0x20000000; // no parameters 35 | const IOC_OUT: c_uint = 0x40000000; // copy out parameters 36 | const IOC_IN: c_uint = 0x80000000; // copy in parameters 37 | const IOC_INOUT: c_uint = IOC_IN|IOC_OUT; 38 | 39 | // Macro for defining all Bhyve ioctl operation constants. 40 | macro_rules! define_ioctl_op { 41 | ($inout:expr, $ioctl_number:expr, $param_size:expr) => { 42 | (($inout) | ((($param_size) & IOCPARM_MASK) << IOCPARM_SIZESHIFT) | (VMM_IOC_GROUP) | ($ioctl_number)) as c_int 43 | }; 44 | } 45 | 46 | // Define constants from machine/vmm_dev.h 47 | 48 | // Identifies ioctl ops for Bhyve 49 | const VMM_IOC_GROUP: c_uint = 118 << 8; // 118 is ASCII for 'v' 50 | 51 | #[repr(C)] 52 | #[allow(non_camel_case_types, unused)] 53 | #[derive(Copy, Clone)] 54 | enum IocNum { 55 | // general routines 56 | IOCNUM_ABIVERS = 0, 57 | IOCNUM_RUN = 1, 58 | IOCNUM_SET_CAPABILITY = 2, 59 | IOCNUM_GET_CAPABILITY = 3, 60 | IOCNUM_SUSPEND = 4, 61 | IOCNUM_REINIT = 5, 62 | 63 | // memory apis 64 | IOCNUM_MAP_MEMORY = 10, // deprecated 65 | IOCNUM_GET_MEMORY_SEG = 11, // deprecated 66 | IOCNUM_GET_GPA_PMAP = 12, 67 | IOCNUM_GLA2GPA = 13, 68 | IOCNUM_ALLOC_MEMSEG = 14, 69 | IOCNUM_GET_MEMSEG = 15, 70 | IOCNUM_MMAP_MEMSEG = 16, 71 | IOCNUM_MMAP_GETNEXT = 17, 72 | IOCNUM_GLA2GPA_NOFAULT = 18, 73 | IOCNUM_MUNMAP_MEMSEG = 19, 74 | 75 | // register/state accessors 76 | IOCNUM_SET_REGISTER = 20, 77 | IOCNUM_GET_REGISTER = 21, 78 | IOCNUM_SET_SEGMENT_DESCRIPTOR = 22, 79 | IOCNUM_GET_SEGMENT_DESCRIPTOR = 23, 80 | IOCNUM_SET_REGISTER_SET = 24, 81 | IOCNUM_GET_REGISTER_SET = 25, 82 | 83 | // interrupt injection 84 | IOCNUM_GET_INTINFO = 28, 85 | IOCNUM_SET_INTINFO = 29, 86 | IOCNUM_INJECT_EXCEPTION = 30, 87 | IOCNUM_LAPIC_IRQ = 31, 88 | IOCNUM_INJECT_NMI = 32, 89 | IOCNUM_IOAPIC_ASSERT_IRQ = 33, 90 | IOCNUM_IOAPIC_DEASSERT_IRQ = 34, 91 | IOCNUM_IOAPIC_PULSE_IRQ = 35, 92 | IOCNUM_LAPIC_MSI = 36, 93 | IOCNUM_LAPIC_LOCAL_IRQ = 37, 94 | IOCNUM_IOAPIC_PINCOUNT = 38, 95 | IOCNUM_RESTART_INSTRUCTION = 39, 96 | 97 | // PCI pass-thru 98 | IOCNUM_BIND_PPTDEV = 40, 99 | IOCNUM_UNBIND_PPTDEV = 41, 100 | IOCNUM_MAP_PPTDEV_MMIO = 42, 101 | IOCNUM_PPTDEV_MSI = 43, 102 | IOCNUM_PPTDEV_MSIX = 44, 103 | IOCNUM_GET_PPTDEV_LIMITS = 45, 104 | 105 | // statistics 106 | IOCNUM_VM_STATS = 50, 107 | IOCNUM_VM_STAT_DESC = 51, 108 | 109 | // kernel device state 110 | IOCNUM_SET_X2APIC_STATE = 60, 111 | IOCNUM_GET_X2APIC_STATE = 61, 112 | IOCNUM_GET_HPET_CAPABILITIES = 62, 113 | 114 | // CPU Topology 115 | IOCNUM_SET_TOPOLOGY = 63, 116 | IOCNUM_GET_TOPOLOGY = 64, 117 | 118 | // legacy interrupt injection 119 | IOCNUM_ISA_ASSERT_IRQ = 80, 120 | IOCNUM_ISA_DEASSERT_IRQ = 81, 121 | IOCNUM_ISA_PULSE_IRQ = 82, 122 | IOCNUM_ISA_SET_IRQ_TRIGGER = 83, 123 | 124 | // vm_cpuset 125 | IOCNUM_ACTIVATE_CPU = 90, 126 | IOCNUM_GET_CPUSET = 91, 127 | IOCNUM_SUSPEND_CPU = 92, 128 | IOCNUM_RESUME_CPU = 93, 129 | 130 | // RTC 131 | IOCNUM_RTC_READ = 100, 132 | IOCNUM_RTC_WRITE = 101, 133 | IOCNUM_RTC_SETTIME = 102, 134 | IOCNUM_RTC_GETTIME = 103, 135 | 136 | // illumos-custom ioctls 137 | IOCNUM_DEVMEM_GETOFFSET = 256, 138 | IOCNUM_WRLOCK_CYCLE = 257, 139 | } 140 | 141 | pub const VM_RUN: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_RUN as c_uint, (size_of::() as c_uint)); 142 | pub const VM_SUSPEND: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SUSPEND as c_uint, (size_of::() as c_uint)); 143 | pub const VM_REINIT: c_int = define_ioctl_op!(IOC_VOID, IocNum::IOCNUM_REINIT as c_uint, 0); 144 | 145 | pub const VM_ALLOC_MEMSEG: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_ALLOC_MEMSEG as c_uint, (size_of::() as c_uint)); 146 | pub const VM_GET_MEMSEG: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_MEMSEG as c_uint, (size_of::() as c_uint)); 147 | 148 | pub const VM_MMAP_MEMSEG: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_MMAP_MEMSEG as c_uint, (size_of::() as c_uint)); 149 | pub const VM_MMAP_GETNEXT: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_MMAP_GETNEXT as c_uint, (size_of::() as c_uint)); 150 | pub const VM_MUNMAP_MEMSEG: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_MUNMAP_MEMSEG as c_uint, (size_of::() as c_uint)); 151 | 152 | pub const VM_SET_REGISTER: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_REGISTER as c_uint, (size_of::() as c_uint)); 153 | pub const VM_GET_REGISTER: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_REGISTER as c_uint, (size_of::() as c_uint)); 154 | pub const VM_SET_SEGMENT_DESCRIPTOR: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_SEGMENT_DESCRIPTOR as c_uint, (size_of::() as c_uint)); 155 | pub const VM_GET_SEGMENT_DESCRIPTOR: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_SEGMENT_DESCRIPTOR as c_uint, (size_of::() as c_uint)); 156 | 157 | pub const VM_SET_CAPABILITY: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_CAPABILITY as c_uint, (size_of::() as c_uint)); 158 | pub const VM_GET_CAPABILITY: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_CAPABILITY as c_uint, (size_of::() as c_uint)); 159 | 160 | pub const VM_SET_X2APIC_STATE: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_X2APIC_STATE as c_uint, (size_of::() as c_uint)); 161 | pub const VM_GET_X2APIC_STATE: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_X2APIC_STATE as c_uint, (size_of::() as c_uint)); 162 | 163 | pub const VM_SET_TOPOLOGY: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_TOPOLOGY as c_uint, (size_of::() as c_uint)); 164 | pub const VM_GET_TOPOLOGY: c_int = define_ioctl_op!(IOC_OUT, IocNum::IOCNUM_GET_TOPOLOGY as c_uint, (size_of::() as c_uint)); 165 | pub const VM_STATS_IOC: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_VM_STATS as c_uint, (size_of::() as c_uint)); 166 | 167 | 168 | pub const VM_ACTIVATE_CPU: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_ACTIVATE_CPU as c_uint, (size_of::() as c_uint)); 169 | pub const VM_SUSPEND_CPU: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SUSPEND_CPU as c_uint, (size_of::() as c_uint)); 170 | pub const VM_RESUME_CPU: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_RESUME_CPU as c_uint, (size_of::() as c_uint)); 171 | 172 | pub const VM_RTC_WRITE: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_RTC_WRITE as c_uint, (size_of::() as c_uint)); 173 | pub const VM_RTC_READ: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_RTC_READ as c_uint, (size_of::() as c_uint)); 174 | pub const VM_RTC_SETTIME: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_RTC_SETTIME as c_uint, (size_of::() as c_uint)); 175 | pub const VM_RTC_GETTIME: c_int = define_ioctl_op!(IOC_OUT, IocNum::IOCNUM_RTC_GETTIME as c_uint, (size_of::() as c_uint)); 176 | 177 | pub const VM_SET_INTINFO: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_SET_INTINFO as c_uint, (size_of::() as c_uint)); 178 | pub const VM_GET_INTINFO: c_int = define_ioctl_op!(IOC_INOUT, IocNum::IOCNUM_GET_INTINFO as c_uint, (size_of::() as c_uint)); 179 | pub const VM_INJECT_EXCEPTION: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_INJECT_EXCEPTION as c_uint, (size_of::() as c_uint)); 180 | pub const VM_INJECT_NMI: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_INJECT_NMI as c_uint, (size_of::() as c_uint)); 181 | pub const VM_LAPIC_IRQ: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_LAPIC_IRQ as c_uint, (size_of::() as c_uint)); 182 | pub const VM_LAPIC_LOCAL_IRQ: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_LAPIC_LOCAL_IRQ as c_uint, (size_of::() as c_uint)); 183 | pub const VM_LAPIC_MSI: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_LAPIC_MSI as c_uint, (size_of::() as c_uint)); 184 | pub const VM_IOAPIC_ASSERT_IRQ: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_IOAPIC_ASSERT_IRQ as c_uint, (size_of::() as c_uint)); 185 | pub const VM_IOAPIC_DEASSERT_IRQ: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_IOAPIC_DEASSERT_IRQ as c_uint, (size_of::() as c_uint)); 186 | pub const VM_IOAPIC_PULSE_IRQ: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_IOAPIC_PULSE_IRQ as c_uint, (size_of::() as c_uint)); 187 | pub const VM_IOAPIC_PINCOUNT: c_int = define_ioctl_op!(IOC_OUT, IocNum::IOCNUM_IOAPIC_PINCOUNT as c_uint, (size_of::() as c_uint)); 188 | pub const VM_RESTART_INSTRUCTION: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_RESTART_INSTRUCTION as c_uint, (size_of::() as c_uint)); 189 | 190 | pub const VM_DEVMEM_GETOFFSET: c_int = define_ioctl_op!(IOC_IN, IocNum::IOCNUM_DEVMEM_GETOFFSET as c_uint, (size_of::() as c_uint)); 191 | 192 | 193 | // ioctls used against ctl device for vm create/destroy 194 | const VMM_IOC_BASE: c_int = (86 << 16) | (77 << 8); // ASCII for 'V' and 'M' 195 | pub const VMM_CREATE_VM: c_int = VMM_IOC_BASE | 0x01; 196 | pub const VMM_DESTROY_VM: c_int = VMM_IOC_BASE | 0x02; 197 | 198 | 199 | // Define structs from machine/vmm_dev.h 200 | 201 | // For VM_MMAP_MEMSEG 202 | #[repr(C)] 203 | #[derive(Copy, Clone, Default)] 204 | pub struct vm_memmap { 205 | pub gpa: c_ulonglong, 206 | pub segid: c_int, // memory segment 207 | pub segoff: c_longlong, // offset into memory segment 208 | pub len: size_t, // mmap length 209 | pub prot: c_int, // RWX 210 | pub flags: c_int, 211 | } 212 | 213 | pub const VM_MEMMAP_F_WIRED: c_int = 0x01; 214 | #[allow(unused)] 215 | pub const VM_MEMMAP_F_IOMMU: c_int = 0x02; 216 | 217 | // For VM_MUNMAP_MEMSEG 218 | #[repr(C)] 219 | pub struct vm_munmap { 220 | pub gpa: c_ulonglong, 221 | pub len: size_t, 222 | } 223 | 224 | 225 | // For VM_ALLOC_MEMSEG and VM_GET_MEMSEG 226 | #[repr(C)] 227 | #[derive(Copy, Clone)] 228 | pub struct vm_memseg { 229 | pub segid: c_int, 230 | pub len: size_t, 231 | pub name: [c_char; SPECNAMELEN + 1], 232 | } 233 | 234 | impl Default for vm_memseg { 235 | fn default() -> vm_memseg { 236 | vm_memseg { 237 | segid: 0, 238 | len: 0, 239 | name: [0 as c_char; SPECNAMELEN + 1], 240 | } 241 | } 242 | } 243 | 244 | // For VM_RTC_SETTIME and VM_RTC_GETTIME 245 | #[repr(C)] 246 | #[derive(Copy, Clone, Default)] 247 | pub struct vm_rtc_time { 248 | pub secs: c_longlong, 249 | } 250 | 251 | // For VM_RTC_WRITE and VM_RTC_READ 252 | #[repr(C)] 253 | #[derive(Copy, Clone, Default)] 254 | pub struct vm_rtc_data { 255 | pub offset: c_int, 256 | pub value: u8, 257 | } 258 | 259 | // For VM_DEVMEM_GETOFFSET 260 | #[repr(C)] 261 | #[derive(Copy, Clone, Default)] 262 | pub struct vm_devmem_offset { 263 | pub segid: c_int, 264 | pub offset: c_longlong, 265 | } 266 | 267 | // For VM_SET_REGISTER and VM_GET_REGISTER 268 | #[repr(C)] 269 | #[derive(Copy, Clone, Default)] 270 | pub struct vm_register { 271 | pub cpuid: c_int, 272 | pub regnum: c_int, // enum vm_reg_name 273 | pub regval: c_ulonglong, 274 | } 275 | 276 | // For VM_SET_SEGMENT_DESCRIPTOR and VM_GET_SEGMENT_DESCRIPTOR 277 | // data or code segment 278 | #[repr(C)] 279 | #[derive(Copy, Clone, Default)] 280 | pub struct vm_seg_desc { 281 | pub cpuid: c_int, 282 | pub regnum: c_int, // enum vm_reg_name 283 | pub desc: seg_desc, // struct seg_desc 284 | } 285 | 286 | // For VM_RUN 287 | #[repr(C)] 288 | #[derive(Copy, Clone, Default)] 289 | pub struct vm_run { 290 | pub cpuid: c_int, 291 | pub vm_exit: vm_exit, 292 | } 293 | 294 | // For VM_SET_CAPABILITY and VM_GET_CAPABILITY 295 | #[repr(C)] 296 | #[derive(Copy, Clone)] 297 | pub struct vm_capability { 298 | pub cpuid: c_int, 299 | pub captype: vm_cap_type, // enum vm_cap_type 300 | pub capval: c_int, 301 | pub allcpus: c_int, 302 | } 303 | 304 | impl Default for vm_capability { 305 | fn default() -> vm_capability { 306 | vm_capability { 307 | cpuid: 0, 308 | captype: vm_cap_type::VM_CAP_MAX, 309 | capval: 0, 310 | allcpus: 0, 311 | } 312 | } 313 | } 314 | 315 | // For VM_GET_X2APIC_STATE and VM_SET_X2APIC_STATE 316 | #[repr(C)] 317 | #[derive(Copy, Clone)] 318 | pub struct vm_x2apic { 319 | pub cpuid: c_int, 320 | pub state: x2apic_state, 321 | } 322 | 323 | impl Default for vm_x2apic { 324 | fn default() -> vm_x2apic { 325 | vm_x2apic { 326 | cpuid: 0, 327 | state: x2apic_state::X2APIC_DISABLED, 328 | } 329 | } 330 | } 331 | 332 | // For VM_SUSPEND 333 | #[repr(C)] 334 | #[derive(Copy, Clone)] 335 | pub struct vm_suspend { 336 | pub how: vm_suspend_how, 337 | } 338 | 339 | // For VM_ACTIVATE_CPU, VM_SUSPEND_CPU, and VM_RESUME_CPU 340 | #[repr(C)] 341 | #[derive(Copy, Clone)] 342 | pub struct vm_activate_cpu { 343 | pub vcpuid: c_int, 344 | } 345 | 346 | // For VM_SET_TOPOLOGY and VM_GET_TOPOLOGY 347 | #[repr(C)] 348 | #[derive(Copy, Clone, Default)] 349 | pub struct vm_cpu_topology { 350 | pub sockets: u16, 351 | pub cores: u16, 352 | pub threads: u16, 353 | pub maxcpus: u16, 354 | } 355 | 356 | const MAX_VM_STATS: usize = 64 + VM_MAXCPU; 357 | 358 | // For VM_STATS_IOC 359 | #[repr(C)] 360 | #[derive(Copy, Clone)] 361 | pub struct vm_stats { 362 | pub cpuid: c_int, // in 363 | pub num_entries: c_int, // out 364 | pub tv: timeval, 365 | pub statbuf: [c_ulonglong; MAX_VM_STATS], 366 | } 367 | 368 | impl Default for vm_stats { 369 | fn default() -> vm_stats { 370 | vm_stats { 371 | cpuid: 0, 372 | num_entries: 0, 373 | tv: timeval::default(), 374 | statbuf: [0; MAX_VM_STATS], 375 | } 376 | } 377 | } 378 | 379 | // For VM_SET_INTINFO and VM_GET_INTINFO 380 | #[repr(C)] 381 | #[derive(Copy, Clone, Default)] 382 | pub struct vm_intinfo { 383 | pub vcpuid: c_int, 384 | pub info1: c_ulonglong, 385 | pub info2: c_ulonglong, 386 | } 387 | 388 | // For VM_INJECT_EXCEPTION 389 | #[repr(C)] 390 | #[derive(Copy, Clone, Default)] 391 | pub struct vm_exception { 392 | pub cpuid: c_int, 393 | pub vector: c_int, 394 | pub error_code: c_uint, 395 | pub error_code_valid: c_int, 396 | pub restart_instruction: c_int, 397 | } 398 | 399 | // For VM_INJECT_NMI 400 | #[repr(C)] 401 | #[derive(Copy, Clone, Default)] 402 | pub struct vm_nmi { 403 | pub cpuid: c_int, 404 | } 405 | 406 | // For VM_LAPIC_MSI 407 | #[repr(C)] 408 | #[derive(Copy, Clone, Default)] 409 | pub struct vm_lapic_msi { 410 | pub msg: c_ulonglong, 411 | pub addr: c_ulonglong, 412 | } 413 | 414 | // For VM_LAPIC_IRQ and VM_LAPIC_LOCAL_IRQ 415 | #[repr(C)] 416 | #[derive(Copy, Clone, Default)] 417 | pub struct vm_lapic_irq { 418 | pub cpuid: c_int, 419 | pub vector: c_int, 420 | } 421 | 422 | // For VM_IOAPIC_ASSERT_IRQ, VM_IOAPIC_DEASSERT_IRQ, and VM_IOAPIC_PULSE_IRQ 423 | #[repr(C)] 424 | #[derive(Copy, Clone, Default)] 425 | pub struct vm_ioapic_irq { 426 | pub irq: c_int, 427 | } 428 | 429 | 430 | #[cfg(test)] 431 | mod tests { 432 | use crate::include::vmm_dev::*; 433 | 434 | #[test] 435 | fn test_ioctl_stats() { 436 | assert_eq!(size_of::(), 0x318); 437 | assert_eq!(VM_STATS_IOC as u32, 0xc0187632); 438 | } 439 | 440 | #[test] 441 | fn test_ioctl_general() { 442 | assert_eq!(size_of::(), 0x90); 443 | assert_eq!(size_of::(), 4); 444 | 445 | //assert_eq!(VM_RUN as u32, 0xc0847601); 446 | assert_eq!(VM_RUN as u32, 0xc0907601); 447 | assert_eq!(VM_SUSPEND as u32, 0x80047604); 448 | assert_eq!(VM_REINIT as u32, 0x20007605); 449 | } 450 | 451 | #[test] 452 | fn test_ioctl_topology() { 453 | assert_eq!(size_of::(), 4); 454 | assert_eq!(size_of::(), 8); 455 | assert_eq!(VM_SET_TOPOLOGY as u32, 0x8008763f); 456 | assert_eq!(VM_GET_TOPOLOGY as u32, 0x40087640); 457 | } 458 | 459 | #[test] 460 | fn test_ioctl_memory() { 461 | assert_eq!(size_of::(), 0x110); 462 | assert_eq!(size_of::(), 0x28); 463 | assert_eq!(VM_ALLOC_MEMSEG as u32, 0x8010760E); 464 | assert_eq!(VM_MMAP_MEMSEG as u32, 0x80287610); 465 | assert_eq!(VM_MMAP_GETNEXT as u32, 0xc0287611); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Rust library interface to Bhyve 2 | //! 3 | //! The Bhyve API library is a userspace interface to the hardware 4 | //! virtualization features in the Illumos kernel provided by Bhyve. It is 5 | //! a minimal interface, which can serve as the lowest userspace base 6 | //! layer for any hypervisor to use Bhyve's hardware virtualization features. It 7 | //! is essentially no more than a clean, safe wrapper around Bhyve's ioctl 8 | //! interface (i.e `/dev/vvmctl`). 9 | //! 10 | //! As part of being a minimal interface, `bhyve-api` avoids external 11 | //! dependencies as much as possible, using only `libc` to access the 12 | //! ioctl interface. The constants and structs required for the Bhyve 13 | //! API are defined in Rust, rather than automatically generating 14 | //! bindings to the C header files, which has benefits for usability 15 | //! and maintainability, and simplifies reasoning from a security 16 | //! perspective. 17 | 18 | pub mod system; 19 | pub mod vm; 20 | mod include; 21 | 22 | pub use vmm_sys_util::errno::Error; 23 | -------------------------------------------------------------------------------- /src/system.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020, Oxide Computer Company 2 | 3 | use libc::{ioctl, open, O_EXCL, O_RDWR, EINVAL}; 4 | use std::ffi::CString; 5 | use std::fs::File; 6 | use std::os::unix::io::{AsRawFd, FromRawFd}; 7 | 8 | use crate::include::vmm_dev::{VMM_CREATE_VM, VMM_DESTROY_VM}; 9 | use crate::Error; 10 | 11 | /// The VMMSystem module handles VMM system operations. It creates and 12 | /// owns the initial filehandle on `/dev/vmmctl`. 13 | /// 14 | /// use bhyve_api::system::*; 15 | /// let system = VMMSystem::new().expect("failed to connect to VMM system ioctl handle"); 16 | /// let vm = system.create_vm("uniquename").expect("failed to create VM"); 17 | /// system.destroy_vm("uniquename").expect("failed to destroy VM"); 18 | 19 | pub struct VMMSystem { 20 | vmmctl: File, 21 | } 22 | 23 | impl VMMSystem { 24 | /// Opens a filehandle to `/dev/vmmctl`, and returns a `Result`. If the open 25 | /// operation fails, the `Result` unwraps as an `Error`. If it succeeds, the 26 | /// `Result` unwraps as an instance of `VMMSystem` for performing VMM system 27 | /// operations. 28 | 29 | pub fn new() -> Result { 30 | let c_path = match CString::new("/dev/vmmctl") { 31 | Ok(s) => s, 32 | Err(_) => return Err(Error::new(EINVAL)) 33 | }; 34 | let raw_fd = unsafe { open(c_path.as_ptr(), O_RDWR | O_EXCL) }; 35 | if raw_fd < 0 { 36 | return Err(Error::last()); 37 | } 38 | let safe_handle = unsafe { File::from_raw_fd(raw_fd) }; 39 | 40 | // Return value is safe because raw file descriptor result is checked 41 | // and ownership of File struct is consumed by KVMSystem struct. 42 | Ok(VMMSystem { 43 | vmmctl: safe_handle, 44 | }) 45 | } 46 | 47 | /// Creates a device for virtual machine operation at `/dev/vmm/[name]`, 48 | /// and returns a `Result`. If the creation operation fails, the `Result` 49 | /// unwraps as an `Error`. If it succeeds, the `Result` unwraps as `i32` 50 | /// integer containing the integer return value of the ioctl operation. 51 | 52 | pub fn create_vm(&self, name: &str) -> Result { 53 | let c_name = match CString::new(name) { 54 | Ok(s) => s, 55 | Err(_) => return Err(Error::new(EINVAL)) 56 | }; 57 | let result = unsafe { ioctl(self.vmmctl.as_raw_fd(), VMM_CREATE_VM, c_name.as_ptr()) }; 58 | if result == -1 { 59 | return Err(Error::last()); 60 | } else { 61 | return Ok(result); 62 | } 63 | } 64 | 65 | /// Destroys a device for virtual machine operations at `/dev/vmm/[name]`, 66 | /// and returns a `Result`. If the destruction operation fails, the `Result` 67 | /// unwraps as an `Error`. If it succeeds, the `Result` unwraps as `i32` 68 | /// integer containing the integer return value of the ioctl operation. 69 | 70 | pub fn destroy_vm(&self, name: &str) -> Result { 71 | let c_name = match CString::new(name) { 72 | Ok(s) => s, 73 | Err(_) => return Err(Error::new(EINVAL)) 74 | }; 75 | let result = unsafe { ioctl(self.vmmctl.as_raw_fd(), VMM_DESTROY_VM, c_name.as_ptr()) }; 76 | if result == -1 { 77 | return Err(Error::last()); 78 | } else { 79 | return Ok(result); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/vm.rs: -------------------------------------------------------------------------------- 1 | //! Bhyve virtual machine operations. 2 | 3 | use libc::{ioctl, open, O_RDWR, c_void, sysconf, _SC_PAGESIZE, EINVAL, EFAULT}; 4 | use std::ffi::{CString, CStr}; 5 | use std::fs::File; 6 | use std::os::unix::io::{AsRawFd, FromRawFd}; 7 | 8 | pub use crate::include::vmm::{vm_cap_type, vm_reg_name}; 9 | use crate::include::vmm::{vm_suspend_how, vm_exitcode, x2apic_state, seg_desc}; 10 | use crate::include::vmm_dev::*; 11 | use crate::include::specialreg::{CR0_NE}; 12 | use crate::Error; 13 | 14 | const MB: u64 = 1024 * 1024; 15 | const GB: u64 = 1024 * MB; 16 | 17 | const MAX_BOOTROM_SIZE: usize = 16 * MB as usize; 18 | 19 | // Size of the guard region before and after the virtual address space 20 | // mapping the guest physical memory. This must be a multiple of the 21 | // superpage size for performance reasons. 22 | //const VM_MMAP_GUARD_SIZE: usize = 4 * MB as usize; 23 | 24 | /// The VirtualMachine module handles Bhyve virtual machine operations. 25 | /// It owns the filehandle for these operations. 26 | pub struct VirtualMachine { 27 | vm: File, 28 | pub name: String, 29 | pub lowmem_limit: usize, 30 | pub memflags: i32, 31 | } 32 | 33 | impl VirtualMachine { 34 | /// Opens a filehandle to an existing virtual machine device by name, and 35 | /// returns a `Result`. If the open operation fails, the `Result` unwraps 36 | /// as an `Error`. If it succeeds, the `Result` unwraps as an instance of 37 | /// `VirtualMachine`. 38 | 39 | pub fn new(name: &str) -> Result { 40 | let path = format!("/dev/vmm/{}", name); 41 | let c_path = match CString::new(path) { 42 | Ok(s) => s, 43 | Err(_) => return Err(Error::new(EINVAL)) 44 | }; 45 | let raw_fd = unsafe { open(c_path.as_ptr(), O_RDWR) }; 46 | if raw_fd < 0 { 47 | return Err(Error::last()); 48 | } 49 | let safe_handle = unsafe { File::from_raw_fd(raw_fd) }; 50 | 51 | // Return value is safe because raw file descriptor result is checked 52 | // and ownership of File struct is consumed by VirtualMachine struct. 53 | Ok(VirtualMachine { 54 | vm: safe_handle, 55 | name: name.to_string(), 56 | lowmem_limit: 3 * GB as usize, 57 | memflags: 0, 58 | }) 59 | } 60 | 61 | /// Map the memory segment identified by 'segid' into the guest address space 62 | /// at [gpa,gpa+len) with protection 'prot'. 63 | pub fn mmap_memseg(&self, gpa: u64, segid: i32, off: i64, len: usize, prot: i32) -> Result { 64 | let mut flags = 0; 65 | if (self.memflags & VM_MEM_F_WIRED) != 0 { 66 | flags = VM_MEMMAP_F_WIRED; 67 | } 68 | 69 | let mem_data = vm_memmap { 70 | gpa: gpa, 71 | segid: segid, 72 | segoff: off, 73 | len: len, 74 | prot: prot, 75 | flags: flags, 76 | }; 77 | 78 | // If this mapping already exists then don't create it again. This 79 | // is the common case for SYSMEM mappings created by bhyveload(8). 80 | match self.mmap_getnext(gpa) { 81 | Ok(exists) => if exists.gpa == mem_data.gpa { 82 | // A memory segment already exists at the same guest physical address 83 | // we are trying to create. 84 | if exists.segid == mem_data.segid && exists.segoff == mem_data.segoff && 85 | exists.prot == mem_data.prot && exists.flags == mem_data.flags { 86 | // The existing memory segment is identical to the one we want to 87 | // create, so do nothing, and return a success value. 88 | return Ok(true); 89 | } else { 90 | // The existing memory segment is not identical to the one we want 91 | // to create, so return an error value. 92 | return Err(Error::new(EFAULT)); 93 | } 94 | } 95 | Err(_) => (), // The memory segment wasn't found, so we should create it 96 | }; 97 | 98 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_MMAP_MEMSEG, &mem_data) }; 99 | if result == 0 { 100 | return Ok(true); 101 | } else { 102 | return Err(Error::last()); 103 | } 104 | } 105 | 106 | /// Iterate over the guest address space. This function finds an address range 107 | /// that starts at an address >= 'gpa'. 108 | /// 109 | /// Returns Ok if the next address range was found and an Error otherwise. 110 | 111 | fn mmap_getnext(&self, gpa: u64) -> Result { 112 | // Struct is allocated (and owned) by Rust, but modified by C 113 | let mut memseg_data = vm_memmap { 114 | gpa: gpa, 115 | ..Default::default() 116 | }; 117 | 118 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_MMAP_GETNEXT, &mut memseg_data) }; 119 | if result == 0 { 120 | return Ok(memseg_data); 121 | } else { 122 | return Err(Error::last()); 123 | } 124 | } 125 | 126 | /// Unmap the memory segment at the guest physical address range [gpa,gpa+len) 127 | pub fn munmap_memseg(&self, gpa: u64, len: usize) -> Result { 128 | // Struct is allocated (and owned) by Rust 129 | let mem_data = vm_munmap { 130 | gpa: gpa, 131 | len: len, 132 | }; 133 | 134 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_MUNMAP_MEMSEG, &mem_data) }; 135 | if result == 0 { 136 | return Ok(true); 137 | } else { 138 | return Err(Error::last()); 139 | } 140 | } 141 | 142 | pub fn alloc_memseg(&self, segid: i32, len: usize, name: &str) -> Result { 143 | let c_name = match CString::new(name) { 144 | Ok(s) => s, 145 | Err(_) => return Err(Error::new(EINVAL)) 146 | }; 147 | 148 | // If the memory segment has already been created then just return. 149 | // This is the usual case for the SYSMEM segment created by userspace 150 | // loaders like bhyveload(8). 151 | match self.get_memseg(segid) { 152 | Ok(exists) => if exists.len != 0 { 153 | // A memory segment already exists with the same segment ID as the one 154 | // we are trying to allocate. 155 | let r_name = unsafe { CStr::from_ptr(exists.name.as_ptr()) }; 156 | let exists_name = r_name.to_owned(); 157 | if exists.len == len && exists_name == c_name { 158 | // The existing memory segment is identical to the one we want to 159 | // allocate, so do nothing, and return a success value. 160 | return Ok(true); 161 | } else { 162 | // The existing memory segment is not identical to the one we want 163 | // to allocate, so return an error value. 164 | return Err(Error::new(EINVAL)); 165 | } 166 | } 167 | Err(e) => return Err(e), 168 | }; 169 | 170 | // Struct is allocated (and owned) by Rust 171 | let mut memseg_data = vm_memseg { 172 | segid: segid, 173 | len: len, 174 | ..Default::default() 175 | }; 176 | 177 | let name_length = name.len(); 178 | if name_length > 0 { 179 | // Don't copy the name if the string is empty (zero length) 180 | if name_length >= memseg_data.name.len() { 181 | // name is too long for vm_memseg struct 182 | return Err(Error::new(EINVAL)); 183 | } else { 184 | // Copy each character from the CString to the char array 185 | for (to, from) in memseg_data.name.iter_mut().zip(c_name.as_bytes_with_nul()) { 186 | *to = *from as i8; 187 | } 188 | } 189 | } 190 | 191 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_ALLOC_MEMSEG, &memseg_data) }; 192 | if result == 0 { 193 | return Ok(true); 194 | } else { 195 | return Err(Error::last()); 196 | } 197 | } 198 | 199 | fn get_memseg(&self, segid: i32) -> Result { 200 | // Struct is allocated (and owned) by Rust, but modified by C 201 | let mut memseg_data = vm_memseg { 202 | segid: segid, 203 | ..Default::default() 204 | }; 205 | 206 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_MEMSEG, &mut memseg_data) }; 207 | if result == 0 { 208 | return Ok(memseg_data); 209 | } else { 210 | return Err(Error::last()); 211 | } 212 | } 213 | 214 | fn add_devmem(&self, segid: i32, name: &str, base: u64, len: usize) -> Result { 215 | self.alloc_memseg(segid, len, name)?; 216 | let mapoff = self.get_devmem_offset(segid)?; 217 | 218 | // let len2 = VM_MMAP_GUARD_SIZE + len + VM_MMAP_GUARD_SIZE; 219 | // let base: *mut u8 = unsafe { 220 | // libc::mmap( 221 | // null_mut(), 222 | // len2, 223 | // libc::PROT_NONE, 224 | // libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE, 225 | // -1, 226 | // 0, 227 | // ) as *mut u8 228 | // }; 229 | 230 | // mmap the devmem region in the host address space 231 | let _ptr: *mut u8 = unsafe { 232 | libc::mmap( 233 | base as *mut c_void, 234 | len, 235 | libc::PROT_READ | libc::PROT_WRITE, 236 | libc::MAP_SHARED | libc::MAP_FIXED, 237 | self.vm.as_raw_fd(), 238 | mapoff, 239 | ) as *mut u8 240 | }; 241 | return Ok(true); 242 | 243 | } 244 | 245 | pub fn add_guest_memory(&self, segid: i32, gpa: u64, base: u64, len: usize, readonly: bool) -> Result { 246 | self.alloc_memseg(segid, len, "")?; // Unnamed memory regions, identified by segment id 247 | 248 | // Map the guest memory into the guest address space 249 | let prot = match readonly { 250 | true => libc::PROT_READ | libc::PROT_EXEC, 251 | false => libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, 252 | }; 253 | self.mmap_memseg(gpa, segid, 0, len, prot)?; 254 | 255 | // mmap into the process address space on the host 256 | let ptr = unsafe { 257 | libc::mmap( 258 | base as *mut c_void, 259 | len, 260 | libc::PROT_READ | libc::PROT_WRITE, 261 | libc::MAP_SHARED | libc::MAP_FIXED, 262 | self.vm.as_raw_fd(), 263 | 0, 264 | ) 265 | }; 266 | if ptr == libc::MAP_FAILED { 267 | return Err(Error::new(EFAULT)); 268 | } 269 | 270 | return Ok(true); 271 | 272 | } 273 | 274 | /// Gets the map offset for the device memory segment 'segid'. 275 | /// 276 | /// Returns Ok containing the offset if successful, and an Error otherwise. 277 | fn get_devmem_offset(&self, segid: i32) -> Result { 278 | // Struct is allocated (and owned) by Rust, but modified by C 279 | let mut memseg_data = vm_devmem_offset { 280 | segid: segid, 281 | ..Default::default() 282 | }; 283 | 284 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_DEVMEM_GETOFFSET, &mut memseg_data) }; 285 | if result == 0 { 286 | return Ok(memseg_data.offset); 287 | } else { 288 | return Err(Error::last()); 289 | } 290 | } 291 | 292 | /// Sets up a memory segment for the bootrom 293 | /// 294 | /// Returns Ok if successful, and an Error otherwise. 295 | pub fn setup_bootrom(&self, base: u64, len: usize) -> Result { 296 | 297 | let page_size: usize = unsafe { sysconf(_SC_PAGESIZE) as usize }; 298 | // Limit bootrom size to 16MB so it doesn't encroach into reserved 299 | // MMIO space (e.g. APIC, HPET, MSI). 300 | if len > MAX_BOOTROM_SIZE || len < page_size { 301 | return Err(Error::new(EINVAL)); 302 | } 303 | // Map the bootrom into the host address space 304 | self.add_devmem(MemSegId::VM_BOOTROM as i32, "bootrom", base, len)?; 305 | 306 | // Map the bootrom into the guest address space 307 | let prot = libc::PROT_READ | libc::PROT_EXEC; 308 | let gpa: u64 = (1 << 32) - len as u64; 309 | self.mmap_memseg(gpa, MemSegId::VM_BOOTROM as i32, 0, len, prot)?; 310 | 311 | Ok(true) 312 | } 313 | 314 | pub fn setup_lowmem(&self, base: u64, len: usize) -> Result { 315 | if len > self.lowmem_limit { 316 | return Err(Error::new(EINVAL)); 317 | } 318 | 319 | let gpa: u64 = 0; 320 | let readonly = false; 321 | // Map the guest memory into the host address space 322 | self.add_guest_memory(MemSegId::VM_LOWMEM as i32, gpa, base, len, readonly)?; 323 | 324 | Ok(true) 325 | } 326 | 327 | pub fn setup_highmem(&self, base: u64, len: usize) -> Result { 328 | let gpa: u64 = 4 * GB; 329 | let readonly = false; 330 | // Map the guest memory into the host address space 331 | self.add_guest_memory(MemSegId::VM_HIGHMEM as i32, gpa, base, len, readonly)?; 332 | 333 | Ok(true) 334 | } 335 | 336 | /// Set the base, limit, and access values of a descriptor register on the VCPU 337 | pub fn set_desc(&self, vcpu_id: i32, reg: vm_reg_name, base: u64, limit: u32, access: u32) -> Result { 338 | // Struct is allocated (and owned) by Rust 339 | let seg_data = vm_seg_desc { 340 | cpuid: vcpu_id, 341 | regnum: reg as i32, 342 | desc: seg_desc {base: base, limit: limit, access: access}, 343 | }; 344 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_SEGMENT_DESCRIPTOR, &seg_data) }; 345 | if result == 0 { 346 | return Ok(true); 347 | } else { 348 | return Err(Error::last()); 349 | } 350 | } 351 | 352 | /// Get the base, limit, and access values of a descriptor register on the VCPU 353 | pub fn get_desc(&self, vcpu_id: i32, reg: vm_reg_name) -> Result<(u64, u32, u32), Error> { 354 | // Struct is allocated (and owned) by Rust, but modified by C 355 | let mut seg_data = vm_seg_desc { 356 | cpuid: vcpu_id, 357 | regnum: reg as i32, 358 | ..Default::default() 359 | }; 360 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_SEGMENT_DESCRIPTOR, &mut seg_data) }; 361 | if result == 0 { 362 | return Ok((seg_data.desc.base, seg_data.desc.limit, seg_data.desc.access)); 363 | } else { 364 | return Err(Error::last()); 365 | } 366 | } 367 | 368 | /// Set the value of a single register on the VCPU 369 | pub fn set_register(&self, vcpu_id: i32, reg: vm_reg_name, val: u64) -> Result { 370 | // Struct is allocated (and owned) by Rust 371 | let reg_data = vm_register { 372 | cpuid: vcpu_id, 373 | regnum: reg as i32, 374 | regval: val, 375 | }; 376 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_REGISTER, ®_data) }; 377 | if result == 0 { 378 | return Ok(true); 379 | } else { 380 | return Err(Error::last()); 381 | } 382 | } 383 | 384 | /// Get the value of a single register on the VCPU 385 | pub fn get_register(&self, vcpu_id: i32, reg: vm_reg_name) -> Result { 386 | // Struct is allocated (and owned) by Rust, but modified by C 387 | let mut reg_data = vm_register { 388 | cpuid: vcpu_id, 389 | regnum: reg as i32, 390 | ..Default::default() 391 | }; 392 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_REGISTER, &mut reg_data) }; 393 | if result == 0 { 394 | return Ok(reg_data.regval); 395 | } else { 396 | return Err(Error::last()); 397 | } 398 | } 399 | 400 | pub fn rtc_write(&self, offset: i32, value: u8) -> Result { 401 | // Struct is allocated (and owned) by Rust 402 | let rtc_data = vm_rtc_data { 403 | offset: offset, 404 | value: value, 405 | }; 406 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RTC_WRITE, &rtc_data) }; 407 | if result == 0 { 408 | return Ok(true); 409 | } else { 410 | return Err(Error::last()); 411 | } 412 | } 413 | 414 | pub fn rtc_read(&self, offset: i32) -> Result { 415 | // Struct is allocated (and owned) by Rust, but modified by C 416 | let mut rtc_data = vm_rtc_data { 417 | offset: offset, 418 | ..Default::default() 419 | }; 420 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RTC_READ, &mut rtc_data) }; 421 | if result == 0 { 422 | return Ok(rtc_data.value); 423 | } else { 424 | return Err(Error::last()); 425 | } 426 | } 427 | 428 | pub fn rtc_settime(&self, secs: i64) -> Result { 429 | // Struct is allocated (and owned) by Rust 430 | let rtc_data = vm_rtc_time { 431 | secs: secs, 432 | }; 433 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RTC_SETTIME, &rtc_data) }; 434 | if result == 0 { 435 | return Ok(true); 436 | } else { 437 | return Err(Error::last()); 438 | } 439 | } 440 | 441 | pub fn rtc_gettime(&self) -> Result { 442 | // Struct is allocated (and owned) by Rust, but modified by C 443 | let mut rtc_data = vm_rtc_time::default(); 444 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RTC_GETTIME, &mut rtc_data) }; 445 | if result == 0 { 446 | return Ok(rtc_data.secs); 447 | } else { 448 | return Err(Error::last()); 449 | } 450 | } 451 | 452 | /// Sets basic attributes of CPUs on the VirtualMachine: sockets, cores, 453 | /// and threads. 454 | pub fn set_topology(&self, sockets: u16, cores: u16, threads: u16) -> Result { 455 | // Struct is allocated (and owned) by Rust 456 | let top_data = vm_cpu_topology { 457 | sockets: sockets, 458 | cores: cores, 459 | threads: threads, 460 | maxcpus: 0, // any other value is invalid 461 | }; 462 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_TOPOLOGY, &top_data) }; 463 | if result == 0 { 464 | return Ok(true); 465 | } else { 466 | return Err(Error::last()); 467 | } 468 | } 469 | 470 | /// Gets current settings for CPUs on the VirtualMachine: sockets, cores, 471 | /// threads, and maximum number of CPUs. 472 | pub fn get_topology(&self) -> Result<(u16, u16, u16, u16), Error> { 473 | // Struct is allocated (and owned) by Rust, but modified by C 474 | let mut top = vm_cpu_topology::default(); 475 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_TOPOLOGY, &mut top) }; 476 | if result == 0 { 477 | return Ok((top.sockets, top.cores, top.threads, top.maxcpus)); 478 | } else { 479 | return Err(Error::last()); 480 | } 481 | } 482 | 483 | /// Gets current stats for a CPUs on the VirtualMachine. 484 | pub fn get_stats(&self, vcpu_id: i32) -> Result { 485 | // Struct is allocated (and owned) by Rust, but modified by C 486 | let mut stats_data = vm_stats { 487 | cpuid: vcpu_id, 488 | ..Default::default() 489 | }; 490 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_STATS_IOC, &mut stats_data) }; 491 | if result == 0 { 492 | return Ok(stats_data.num_entries); 493 | } else { 494 | return Err(Error::last()); 495 | } 496 | } 497 | 498 | /// Activates a Virtual CPU on the VirtualMachine. 499 | pub fn activate_vcpu(&self, vcpu_id: i32) -> Result { 500 | // Struct is allocated (and owned) by Rust 501 | let cpu_data = vm_activate_cpu { vcpuid: vcpu_id }; 502 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_ACTIVATE_CPU, &cpu_data) }; 503 | if result == 0 { 504 | return Ok(true); 505 | } else { 506 | return Err(Error::last()); 507 | } 508 | } 509 | 510 | pub fn set_x2apic_state(&self, vcpu_id: i32, enable: bool) -> Result { 511 | let state = match enable { 512 | true => x2apic_state::X2APIC_ENABLED, 513 | false => x2apic_state::X2APIC_DISABLED 514 | }; 515 | 516 | // Struct is allocated (and owned) by Rust 517 | let x2apic_data = vm_x2apic { 518 | cpuid: vcpu_id, 519 | state: state, 520 | }; 521 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_X2APIC_STATE, &x2apic_data) }; 522 | if result == 0 { 523 | return Ok(true); 524 | } else { 525 | return Err(Error::last()); 526 | } 527 | } 528 | 529 | pub fn get_x2apic_state(&self, vcpu_id: i32) -> Result { 530 | // Struct is allocated (and owned) by Rust, but modified by C 531 | let mut x2apic_data = vm_x2apic { 532 | cpuid: vcpu_id, 533 | ..Default::default() 534 | }; 535 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_X2APIC_STATE, &mut x2apic_data) }; 536 | if result == 0 { 537 | match x2apic_data.state { 538 | x2apic_state::X2APIC_ENABLED => return Ok(true), 539 | x2apic_state::X2APIC_DISABLED => return Ok(false), 540 | x2apic_state::X2APIC_STATE_LAST => return Err(Error::new(EINVAL)), 541 | } 542 | } else { 543 | return Err(Error::last()); 544 | } 545 | } 546 | 547 | /// From Intel Vol 3a: 548 | /// Table 9-1. IA-32 Processor States Following Power-up, Reset or INIT 549 | pub fn vcpu_reset(&self, vcpu_id: i32) -> Result { 550 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RFLAGS, 0x2)?; 551 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RIP, 0xfff0)?; 552 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_CR0, CR0_NE)?; 553 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_CR3, 0)?; 554 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_CR4, 0)?; 555 | 556 | // CS: present, r/w, accessed, 16-bit, byte granularity, usable 557 | let cs_base = 0xffff0000; 558 | let cs_limit = 0xffff; 559 | let cs_access = 0x0093; 560 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_CS, cs_base, cs_limit, cs_access)?; 561 | 562 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_CS, 0xf000)?; 563 | 564 | 565 | // SS,DS,ES,FS,GS: present, r/w, accessed, 16-bit, byte granularity 566 | let desc_base = 0; 567 | let desc_limit = 0xffff; 568 | let desc_access = 0x0093; 569 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_SS, desc_base, desc_limit, desc_access)?; 570 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_DS, desc_base, desc_limit, desc_access)?; 571 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_ES, desc_base, desc_limit, desc_access)?; 572 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_FS, desc_base, desc_limit, desc_access)?; 573 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_GS, desc_base, desc_limit, desc_access)?; 574 | 575 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_SS, 0)?; 576 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_DS, 0)?; 577 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_ES, 0)?; 578 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_FS, 0)?; 579 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_GS, 0)?; 580 | 581 | // General purpose registers 582 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RAX, 0)?; 583 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RBX, 0)?; 584 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RCX, 0)?; 585 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RDX, 0xf00)?; 586 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RSI, 0)?; 587 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RDI, 0)?; 588 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RBP, 0)?; 589 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_RSP, 0)?; 590 | 591 | 592 | // GDTR, IDTR 593 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_GDTR, 0, 0xffff, 0)?; 594 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_IDTR, 0, 0xffff, 0)?; 595 | 596 | // TR 597 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_TR, 0, 0, 0x0000008b)?; 598 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_TR, 0)?; 599 | 600 | // LDTR 601 | self.set_desc(vcpu_id, vm_reg_name::VM_REG_GUEST_LDTR, 0, 0xffff, 0x00000082)?; 602 | self.set_register(vcpu_id, vm_reg_name::VM_REG_GUEST_LDTR, 0)?; 603 | 604 | Ok(true) 605 | } 606 | 607 | /// Suspends a Virtual CPU on the VirtualMachine. 608 | pub fn suspend_vcpu(&self, vcpu_id: i32) -> Result { 609 | // Struct is allocated (and owned) by Rust 610 | let cpu_data = vm_activate_cpu { vcpuid: vcpu_id }; 611 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SUSPEND_CPU, &cpu_data) }; 612 | if result == 0 { 613 | return Ok(true); 614 | } else { 615 | return Err(Error::last()); 616 | } 617 | } 618 | 619 | /// Resumes a Virtual CPU on the VirtualMachine. 620 | pub fn resume_vcpu(&self, vcpu_id: i32) -> Result { 621 | // Struct is allocated (and owned) by Rust 622 | let cpu_data = vm_activate_cpu { vcpuid: vcpu_id }; 623 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RESUME_CPU, &cpu_data) }; 624 | if result == 0 { 625 | return Ok(true); 626 | } else { 627 | return Err(Error::last()); 628 | } 629 | } 630 | 631 | /// Runs the VirtualMachine, and returns an exit reason. 632 | pub fn run(&self, vcpu_id: i32) -> Result { 633 | // Struct is allocated (and owned) by Rust, but modified by C 634 | let mut run_data = vm_run { 635 | cpuid: vcpu_id, 636 | ..Default::default() 637 | }; 638 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RUN, &mut run_data) }; 639 | if result == 0 { 640 | let rip = run_data.vm_exit.rip; 641 | println!("RIP after run is {}", rip); 642 | let cid = run_data.cpuid; 643 | println!("VCPU ID is {}", cid); 644 | match run_data.vm_exit.exitcode { 645 | vm_exitcode::VM_EXITCODE_INOUT => { 646 | // Safe because the exit code told us which union field to use. 647 | let io = unsafe { run_data.vm_exit.u.inout }; 648 | let port = io.port; 649 | let value = io.eax; 650 | let bytes = io.bytes(); 651 | 652 | if io.is_in() { 653 | return Ok(VmExit::IoIn(port, bytes)); 654 | } else { 655 | return Ok(VmExit::IoOut(port, bytes, value)); 656 | } 657 | } 658 | vm_exitcode::VM_EXITCODE_INOUT_STR => { 659 | // Safe because the exit code told us which union field to use. 660 | let vis = unsafe { run_data.vm_exit.u.inout_str }; 661 | let io = vis.inout; 662 | let port = io.port; 663 | 664 | if !io.is_string() { 665 | return Err(Error::new(EINVAL)); 666 | } 667 | 668 | let mask: u64 = match vis.addrsize { 669 | 2 => 0xffff, 670 | 4 => 0xffffffff, 671 | 8 => 0xffffffffffffffff, 672 | _ => return Err(Error::new(EINVAL)) 673 | }; 674 | 675 | let index: u64 = vis.index & mask; 676 | let count: u64 = vis.count & mask; 677 | 678 | let bytes = io.bytes(); 679 | let repeat = io.is_repeat(); 680 | if io.is_in() { 681 | return Ok(VmExit::IoInStr(port, bytes, index, count, repeat)); 682 | } else { 683 | return Ok(VmExit::IoOutStr(port, bytes, index, count, repeat)); 684 | } 685 | } 686 | vm_exitcode::VM_EXITCODE_VMX => { 687 | let status = unsafe { run_data.vm_exit.u.vmx.status }; 688 | let reason = unsafe { run_data.vm_exit.u.vmx.exit_reason }; 689 | let qual = unsafe { run_data.vm_exit.u.vmx.exit_qualification }; 690 | let inst_type = unsafe { run_data.vm_exit.u.vmx.inst_type }; 691 | let inst_error = unsafe { run_data.vm_exit.u.vmx.inst_error }; 692 | return Ok(VmExit::Vmx(status, reason, qual, inst_type, inst_error)); 693 | } 694 | vm_exitcode::VM_EXITCODE_BOGUS => { 695 | return Ok(VmExit::Bogus); 696 | } 697 | vm_exitcode::VM_EXITCODE_RDMSR => { 698 | return Ok(VmExit::RdMsr); 699 | } 700 | vm_exitcode::VM_EXITCODE_WRMSR => { 701 | return Ok(VmExit::WrMsr); 702 | } 703 | vm_exitcode::VM_EXITCODE_HLT => { 704 | return Ok(VmExit::Halt); 705 | } 706 | vm_exitcode::VM_EXITCODE_MTRAP => { 707 | return Ok(VmExit::Mtrap); 708 | } 709 | vm_exitcode::VM_EXITCODE_PAUSE => { 710 | return Ok(VmExit::Pause); 711 | } 712 | vm_exitcode::VM_EXITCODE_PAGING => { 713 | return Ok(VmExit::Paging); 714 | } 715 | vm_exitcode::VM_EXITCODE_INST_EMUL => { 716 | return Ok(VmExit::InstEmul); 717 | } 718 | vm_exitcode::VM_EXITCODE_SPINUP_AP => { 719 | return Ok(VmExit::SpinupAp); 720 | } 721 | vm_exitcode::VM_EXITCODE_DEPRECATED1 => { 722 | return Ok(VmExit::Deprecated); 723 | } 724 | vm_exitcode::VM_EXITCODE_RUNBLOCK => { 725 | return Ok(VmExit::RunBlock); 726 | } 727 | vm_exitcode::VM_EXITCODE_IOAPIC_EOI => { 728 | let ioapic = unsafe { run_data.vm_exit.u.ioapic_eoi }; 729 | return Ok(VmExit::IoapicEoi(ioapic.vector)); 730 | } 731 | vm_exitcode::VM_EXITCODE_SUSPENDED => { 732 | return Ok(VmExit::Suspended); 733 | } 734 | vm_exitcode::VM_EXITCODE_TASK_SWITCH => { 735 | return Ok(VmExit::TaskSwitch); 736 | } 737 | vm_exitcode::VM_EXITCODE_MONITOR => { 738 | return Ok(VmExit::Monitor); 739 | } 740 | vm_exitcode::VM_EXITCODE_MWAIT => { 741 | return Ok(VmExit::Mwait); 742 | } 743 | vm_exitcode::VM_EXITCODE_SVM => { 744 | let svm = unsafe { run_data.vm_exit.u.svm }; 745 | return Ok(VmExit::Svm(svm.exitcode, svm.exitinfo1, svm.exitinfo2)); 746 | } 747 | vm_exitcode::VM_EXITCODE_REQIDLE => { 748 | return Ok(VmExit::ReqIdle); 749 | } 750 | vm_exitcode::VM_EXITCODE_DEBUG => { 751 | return Ok(VmExit::Debug); 752 | } 753 | vm_exitcode::VM_EXITCODE_VMINSN => { 754 | return Ok(VmExit::VmInsn); 755 | } 756 | vm_exitcode::VM_EXITCODE_HT => { 757 | return Ok(VmExit::Ht); 758 | } 759 | vm_exitcode::VM_EXITCODE_MAX => { 760 | return Ok(VmExit::Max); 761 | } 762 | } 763 | } else { 764 | return Err(Error::last()); 765 | } 766 | } 767 | 768 | /// Resets the VirtualMachine. 769 | pub fn reset(&self) -> Result { 770 | let suspend_data = vm_suspend { how: vm_suspend_how::VM_SUSPEND_RESET }; 771 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SUSPEND, &suspend_data) }; 772 | if result == 0 { 773 | return Ok(result); 774 | } else { 775 | return Err(Error::last()); 776 | } 777 | } 778 | 779 | /// Halts the VirtualMachine. 780 | pub fn halt(&self) -> Result { 781 | let suspend_data = vm_suspend { how: vm_suspend_how::VM_SUSPEND_HALT }; 782 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SUSPEND, &suspend_data) }; 783 | if result == 0 { 784 | return Ok(result); 785 | } else { 786 | return Err(Error::last()); 787 | } 788 | } 789 | 790 | /// Suspends the VirtualMachine with power off. 791 | pub fn poweroff(&self) -> Result { 792 | let suspend_data = vm_suspend { how: vm_suspend_how::VM_SUSPEND_POWEROFF }; 793 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SUSPEND, &suspend_data) }; 794 | if result == 0 { 795 | return Ok(result); 796 | } else { 797 | return Err(Error::last()); 798 | } 799 | } 800 | 801 | /// Suspends the VirtualMachine with triple fault. 802 | pub fn triplefault(&self) -> Result { 803 | let suspend_data = vm_suspend { how: vm_suspend_how::VM_SUSPEND_TRIPLEFAULT }; 804 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SUSPEND, &suspend_data) }; 805 | if result == 0 { 806 | return Ok(result); 807 | } else { 808 | return Err(Error::last()); 809 | } 810 | } 811 | 812 | /// Reinitializes the VirtualMachine. 813 | pub fn reinit(&self) -> Result { 814 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_REINIT) }; 815 | if result == 0 { 816 | return Ok(result); 817 | } else { 818 | return Err(Error::last()); 819 | } 820 | } 821 | 822 | /// Get the value of an optional capability on the VCPU 823 | pub fn get_capability(&self, vcpu_id: i32, cap: vm_cap_type) -> Result { 824 | // Struct is allocated (and owned) by Rust, but modified by C 825 | let mut cap_data = vm_capability { 826 | cpuid: vcpu_id, 827 | captype: cap, 828 | ..Default::default() 829 | }; 830 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_CAPABILITY, &mut cap_data) }; 831 | if result == 0 { 832 | return Ok(cap_data.capval); 833 | } else { 834 | return Err(Error::last()); 835 | } 836 | } 837 | 838 | /// Set the value of an optional capability on the VCPU 839 | pub fn set_capability(&self, vcpu_id: i32, cap: vm_cap_type, val: i32) -> Result { 840 | // Struct is allocated (and owned) by Rust 841 | let cap_data = vm_capability { 842 | cpuid: vcpu_id, 843 | captype: cap, 844 | capval: val, 845 | ..Default::default() 846 | }; 847 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_CAPABILITY, &cap_data) }; 848 | if result == 0 { 849 | return Ok(true); 850 | } else { 851 | return Err(Error::last()); 852 | } 853 | } 854 | 855 | /// Set interrupt info on the VCPU 856 | pub fn set_intinfo(&self, vcpu_id: i32, info1: u64) -> Result { 857 | // Struct is allocated (and owned) by Rust 858 | let intinfo_data = vm_intinfo { 859 | vcpuid: vcpu_id, 860 | info1: info1, 861 | ..Default::default() 862 | }; 863 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_SET_INTINFO, &intinfo_data) }; 864 | if result == 0 { 865 | return Ok(true); 866 | } else { 867 | return Err(Error::last()); 868 | } 869 | } 870 | 871 | /// Get the interrupt info on the VCPU 872 | pub fn get_intinfo(&self, vcpu_id: i32) -> Result<(u64, u64), Error> { 873 | // Struct is allocated (and owned) by Rust, but modified by C 874 | let mut intinfo_data = vm_intinfo { 875 | vcpuid: vcpu_id, 876 | ..Default::default() 877 | }; 878 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_GET_INTINFO, &mut intinfo_data) }; 879 | if result == 0 { 880 | return Ok((intinfo_data.info1, intinfo_data.info2)); 881 | } else { 882 | return Err(Error::last()); 883 | } 884 | } 885 | 886 | /// Inject an exception on the VCPU 887 | pub fn inject_exception(&self, vcpu_id: i32, vector: i32, valid: i32, errcode: u32, restart: i32) -> Result { 888 | // Struct is allocated (and owned) by Rust 889 | let exc_data = vm_exception { 890 | cpuid: vcpu_id, 891 | vector: vector, 892 | error_code: errcode, 893 | error_code_valid: valid, 894 | restart_instruction: restart, 895 | }; 896 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_INJECT_EXCEPTION, &exc_data) }; 897 | if result == 0 { 898 | return Ok(true); 899 | } else { 900 | return Err(Error::last()); 901 | } 902 | } 903 | 904 | /// Inject non-maskable interrupt (NMI) on the VCPU 905 | pub fn inject_nmi(&self, vcpu_id: i32) -> Result { 906 | // Struct is allocated (and owned) by Rust 907 | let nmi_data = vm_nmi { 908 | cpuid: vcpu_id, 909 | }; 910 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_INJECT_NMI, &nmi_data) }; 911 | if result == 0 { 912 | return Ok(true); 913 | } else { 914 | return Err(Error::last()); 915 | } 916 | } 917 | 918 | /// Signal to the Local Advanced Programmable Interrupt Controller (LAPIC) 919 | /// that an interrupt request (IRQ) at 'vector' needs to be sent to the VCPU 920 | /// identified by 'vcpu_id'. The state of the interrupt request is recorded in 921 | /// the LAPIC interrupt request register (IRR). 922 | pub fn lapic_irq(&self, vcpu_id: i32, vector: i32) -> Result { 923 | // Struct is allocated (and owned) by Rust 924 | let irq_data = vm_lapic_irq { 925 | cpuid: vcpu_id, 926 | vector: vector, 927 | }; 928 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_LAPIC_IRQ, &irq_data) }; 929 | if result == 0 { 930 | return Ok(true); 931 | } else { 932 | return Err(Error::last()); 933 | } 934 | } 935 | 936 | /// Trigger an interrupt request (IRQ) according to the local vector table 937 | /// (LVT) on the Local Advanced Programmable Interrupt Controller (LAPIC) 938 | /// for the VCPU identified by 'vcpu_id'. The 'vcpu_id' can be set to -1 to 939 | /// trigger the interrupt on all VCPUs. 940 | pub fn lapic_local_irq(&self, vcpu_id: i32, vector: i32) -> Result { 941 | // Struct is allocated (and owned) by Rust 942 | let irq_data = vm_lapic_irq { 943 | cpuid: vcpu_id, 944 | vector: vector, 945 | }; 946 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_LAPIC_LOCAL_IRQ, &irq_data) }; 947 | if result == 0 { 948 | return Ok(true); 949 | } else { 950 | return Err(Error::last()); 951 | } 952 | } 953 | 954 | /// Signal an interrupt using Message Signaled Interrupts (MSI) 955 | pub fn lapic_msi(&self, addr: u64, msg: u64) -> Result { 956 | // Struct is allocated (and owned) by Rust 957 | let msi_data = vm_lapic_msi { 958 | msg: msg, 959 | addr: addr, 960 | }; 961 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_LAPIC_MSI, &msi_data) }; 962 | if result == 0 { 963 | return Ok(true); 964 | } else { 965 | return Err(Error::last()); 966 | } 967 | } 968 | 969 | /// Set the I/O APIC pin state for an interrupt request (IRQ) on the VM to true. 970 | pub fn ioapic_assert_irq(&self, irq: i32) -> Result { 971 | // Struct is allocated (and owned) by Rust 972 | let irq_data = vm_ioapic_irq { 973 | irq: irq, 974 | }; 975 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_IOAPIC_ASSERT_IRQ, &irq_data) }; 976 | if result == 0 { 977 | return Ok(true); 978 | } else { 979 | return Err(Error::last()); 980 | } 981 | } 982 | 983 | /// Set the I/O APIC pin state for an interrupt request (IRQ) on the VM to false. 984 | pub fn ioapic_deassert_irq(&self, irq: i32) -> Result { 985 | // Struct is allocated (and owned) by Rust 986 | let irq_data = vm_ioapic_irq { 987 | irq: irq, 988 | }; 989 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_IOAPIC_DEASSERT_IRQ, &irq_data) }; 990 | if result == 0 { 991 | return Ok(true); 992 | } else { 993 | return Err(Error::last()); 994 | } 995 | } 996 | 997 | /// Set the I/O APIC pin state for an interrupt request (IRQ) on the VM to 998 | /// true and then false (a "pulse"). 999 | pub fn ioapic_pulse_irq(&self, irq: i32) -> Result { 1000 | // Struct is allocated (and owned) by Rust 1001 | let irq_data = vm_ioapic_irq { 1002 | irq: irq, 1003 | }; 1004 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_IOAPIC_PULSE_IRQ, &irq_data) }; 1005 | if result == 0 { 1006 | return Ok(true); 1007 | } else { 1008 | return Err(Error::last()); 1009 | } 1010 | } 1011 | 1012 | /// Get the I/O APIC pincount for the VM 1013 | pub fn ioapic_pincount(&self) -> Result { 1014 | // Integer is allocated (and owned) by Rust, but modified by C 1015 | let mut pincount: i32 = 0; 1016 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_IOAPIC_PINCOUNT, &mut pincount) }; 1017 | if result == 0 { 1018 | return Ok(pincount); 1019 | } else { 1020 | return Err(Error::last()); 1021 | } 1022 | } 1023 | 1024 | /// Restart the current instruction on the VCPU 1025 | pub fn restart_instruction(&self, vcpu_id: i32) -> Result { 1026 | // Integer is allocated (and owned) by Rust 1027 | let result = unsafe { ioctl(self.vm.as_raw_fd(), VM_RESTART_INSTRUCTION, &vcpu_id) }; 1028 | if result == 0 { 1029 | return Ok(true); 1030 | } else { 1031 | return Err(Error::last()); 1032 | } 1033 | } 1034 | } 1035 | 1036 | // Different styles of mapping the memory assigned to a VM into the address 1037 | // space of the controlling process. 1038 | #[repr(C)] 1039 | #[allow(non_camel_case_types, unused)] 1040 | #[derive(Debug, Copy, Clone)] 1041 | enum vm_mmap_style { 1042 | VM_MMAP_NONE, /* no mapping */ 1043 | VM_MMAP_ALL, /* fully and statically mapped */ 1044 | VM_MMAP_SPARSE, /* mappings created on-demand */ 1045 | } 1046 | 1047 | // 'flags' value passed to 'vm_set_memflags()'. 1048 | //const VM_MEM_F_INCORE: i32 = 0x01; // include guest memory in core file 1049 | const VM_MEM_F_WIRED: i32 = 0x02; // guest memory is wired 1050 | 1051 | /// Identifiers for memory segments, both system memory and devmem segments. 1052 | #[repr(C)] 1053 | #[allow(non_camel_case_types, unused)] 1054 | #[derive(Debug, Copy, Clone)] 1055 | pub enum MemSegId{ 1056 | VM_LOWMEM = 0, 1057 | VM_HIGHMEM = 1, 1058 | VM_BOOTROM = 2, 1059 | VM_FRAMEBUFFER = 3, 1060 | } 1061 | 1062 | /// Reasons for virtual machine exits. 1063 | /// 1064 | /// The exit reasons are mapped to the `VM_EXIT_*` defines in `machine/vmm.h`. 1065 | /// 1066 | #[derive(Debug)] 1067 | pub enum VmExit { 1068 | IoIn(u16 /* port */, u16 /* bytes */), 1069 | IoOut(u16 /* port */, u16 /* bytes */, u32 /* value */), 1070 | IoInStr(u16 /* port */, u16 /* bytes */, u64 /* index */, u64 /* count */, bool /* repeat */), 1071 | IoOutStr(u16 /* port */, u16 /* bytes */, u64 /* index */, u64 /* count */, bool /* repeat */), 1072 | Vmx(i32 /* status */, u32 /* exit reason */, u64 /* exit qualification */, i32 /* instruction type */, i32 /* instruction error */), 1073 | Bogus, 1074 | RdMsr, 1075 | WrMsr, 1076 | Halt, 1077 | Mtrap, 1078 | Pause, 1079 | Paging, 1080 | InstEmul, 1081 | SpinupAp, 1082 | Deprecated, 1083 | RunBlock, 1084 | IoapicEoi(i32 /* vector */), 1085 | Suspended, 1086 | TaskSwitch, 1087 | Monitor, 1088 | Mwait, 1089 | Svm(u64 /* exitcode */, u64 /* exitinfo1 */, u64 /* exitinfo2 */), 1090 | ReqIdle, 1091 | Debug, 1092 | VmInsn, 1093 | Ht, 1094 | Max, 1095 | } 1096 | -------------------------------------------------------------------------------- /tests/api.rs: -------------------------------------------------------------------------------- 1 | extern crate bhyve_api; 2 | 3 | use bhyve_api::system::*; 4 | use bhyve_api::vm::*; 5 | 6 | #[test] 7 | fn test_create_vm() { 8 | let vm_name = "testname"; 9 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 10 | vmmctl.create_vm(vm_name).expect("failed to create VM device"); 11 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 12 | assert_eq!(vm.name, "testname"); 13 | vmmctl.destroy_vm(vm_name).expect("failed to destroy VM"); 14 | } 15 | -------------------------------------------------------------------------------- /tests/registers.rs: -------------------------------------------------------------------------------- 1 | extern crate bhyve_api; 2 | 3 | use bhyve_api::system::*; 4 | use bhyve_api::vm::*; 5 | 6 | const TEST_CPUID: i32 = 0; 7 | 8 | fn setup_vm(vm_name: &str) -> VirtualMachine { 9 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 10 | vmmctl.create_vm(vm_name).expect("failed to create VM device"); 11 | let vm = VirtualMachine::new(vm_name).expect("failed to open filehandle to VM device"); 12 | return vm; 13 | } 14 | 15 | fn teardown_vm(vm_name: &str) { 16 | let vmmctl = VMMSystem::new().expect("failed to create VMM system ioctl handle"); 17 | vmmctl.destroy_vm(vm_name).expect("failed to destroy VM"); 18 | } 19 | 20 | #[test] 21 | fn test_caller_save_registers() { 22 | let testname = "test_caller_save_registers"; 23 | let vm = setup_vm(testname); 24 | 25 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RAX, 1100).expect("failed to set RAX register"); 26 | let rax = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RAX).expect("failed to get RAX register"); 27 | assert_eq!(rax, 1100); 28 | 29 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RCX, 1200).expect("failed to set RCX register"); 30 | let rcx = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RCX).expect("failed to get RCX register"); 31 | assert_eq!(rcx, 1200); 32 | 33 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RDX, 1300).expect("failed to set RDX register"); 34 | let rdx = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RDX).expect("failed to get RDX register"); 35 | assert_eq!(rdx, 1300); 36 | 37 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RDI, 1400).expect("failed to set RDI register"); 38 | let rdi = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RDI).expect("failed to get RDI register"); 39 | assert_eq!(rdi, 1400); 40 | 41 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RSI, 1500).expect("failed to set RSI register"); 42 | let rsi = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RSI).expect("failed to get RSI register"); 43 | assert_eq!(rsi, 1500); 44 | 45 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RSP, 1600).expect("failed to set RSP register"); 46 | let rsp = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RSP).expect("failed to get RSP register"); 47 | assert_eq!(rsp, 1600); 48 | 49 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R8, 1008).expect("failed to set R8 register"); 50 | let r8 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R8).expect("failed to get R8 register"); 51 | assert_eq!(r8, 1008); 52 | 53 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R9, 1009).expect("failed to set R9 register"); 54 | let r9 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R9).expect("failed to get R9 register"); 55 | assert_eq!(r9, 1009); 56 | 57 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R10, 1010).expect("failed to set R10 register"); 58 | let r10 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R10).expect("failed to get R10 register"); 59 | assert_eq!(r10, 1010); 60 | 61 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R11, 1011).expect("failed to set R11 register"); 62 | let r11 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R11).expect("failed to get R11 register"); 63 | assert_eq!(r11, 1011); 64 | 65 | teardown_vm(testname); 66 | } 67 | 68 | #[test] 69 | fn test_callee_save_registers() { 70 | let testname = "test_callee_save_registers"; 71 | let vm = setup_vm(testname); 72 | 73 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RBX, 2100).expect("failed to set RBX register"); 74 | let rbx = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RBX).expect("failed to get RBX register"); 75 | assert_eq!(rbx, 2100); 76 | 77 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RBP, 2200).expect("failed to set RBP register"); 78 | let rbp = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RBP).expect("failed to get RBP register"); 79 | assert_eq!(rbp, 2200); 80 | 81 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R12, 2012).expect("failed to set R12 register"); 82 | let r12 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R12).expect("failed to get R12 register"); 83 | assert_eq!(r12, 2012); 84 | 85 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R13, 2013).expect("failed to set R13 register"); 86 | let r13 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R13).expect("failed to get R13 register"); 87 | assert_eq!(r13, 2013); 88 | 89 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R14, 2014).expect("failed to set R14 register"); 90 | let r14 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R14).expect("failed to get R14 register"); 91 | assert_eq!(r14, 2014); 92 | 93 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R15, 2015).expect("failed to set R15 register"); 94 | let r15 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_R15).expect("failed to get R15 register"); 95 | assert_eq!(r15, 2015); 96 | 97 | teardown_vm(testname); 98 | } 99 | 100 | #[test] 101 | fn test_debug_registers() { 102 | let testname = "test_debug_registers"; 103 | let vm = setup_vm(testname); 104 | 105 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR0, 3000).expect("failed to set DR0 register"); 106 | let dr0 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR0).expect("failed to get DR0 register"); 107 | assert_eq!(dr0, 3000); 108 | 109 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR1, 3001).expect("failed to set DR1 register"); 110 | let dr1 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR1).expect("failed to get DR1 register"); 111 | assert_eq!(dr1, 3001); 112 | 113 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR2, 3002).expect("failed to set DR2 register"); 114 | let dr2 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR2).expect("failed to get DR2 register"); 115 | assert_eq!(dr2, 3002); 116 | 117 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR3, 3003).expect("failed to set DR3 register"); 118 | let dr3 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR3).expect("failed to get DR3 register"); 119 | assert_eq!(dr3, 3003); 120 | 121 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR6, 3006).expect("failed to set DR6 register"); 122 | let dr6 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR6).expect("failed to get DR6 register"); 123 | assert_eq!(dr6, 3006); 124 | 125 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR7, 3007).expect("failed to set DR7 register"); 126 | let dr7 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_DR7).expect("failed to get DR7 register"); 127 | assert_eq!(dr7, 3007); 128 | 129 | teardown_vm(testname); 130 | } 131 | 132 | #[test] 133 | fn test_control_registers() { 134 | let testname = "test_control_registers"; 135 | let vm = setup_vm(testname); 136 | 137 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR0, 4000).expect("failed to set CR0 register"); 138 | let cr0 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR0).expect("failed to get CR0 register"); 139 | assert_eq!(cr0, 4000); 140 | 141 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR2, 4002).expect("failed to set CR2 register"); 142 | let cr2 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR2).expect("failed to get CR2 register"); 143 | assert_eq!(cr2, 4002); 144 | 145 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR3, 4003).expect("failed to set CR3 register"); 146 | let cr3 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR3).expect("failed to get CR3 register"); 147 | assert_eq!(cr3, 4003); 148 | 149 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR4, 4004).expect("failed to set CR4 register"); 150 | let cr4 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR4).expect("failed to get CR4 register"); 151 | assert_eq!(cr4, 4004); 152 | 153 | // NOT SUPPORTED 154 | // 155 | // CR8 or TPR (task priority register) is a new register introduced in AMD64 156 | // to speed interrupt management. 157 | // See "AMD64 Architecture Programmer's Manual, Volume 2: System Programming", 158 | // section 2.6.5 "Task-Priority Register (CR8)" and figure 1-7 "System Registers". 159 | // 160 | // vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR8, 4008).expect("failed to set CR8 register"); 161 | // let cr8 = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_CR8).expect("failed to get CR8 register"); 162 | // assert_eq!(cr8, 4008); 163 | 164 | teardown_vm(testname); 165 | } 166 | 167 | #[test] 168 | fn test_system_flags_register() { 169 | let testname = "test_system_flags_register"; 170 | let vm = setup_vm(testname); 171 | 172 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RFLAGS, 5000).expect("failed to set RFLAGS register"); 173 | let rflags = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_RFLAGS).expect("failed to get RFLAGS register"); 174 | assert_eq!(rflags, 5000); 175 | 176 | teardown_vm(testname); 177 | } 178 | 179 | #[test] 180 | fn test_descriptor_table_registers() { 181 | let testname = "test_descriptor_table_registers"; 182 | let vm = setup_vm(testname); 183 | 184 | // Global Descriptor-Table Register 185 | vm.set_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_GDTR, 0, 0xffff, 0).expect("failed to set GDTR register"); 186 | match vm.get_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_GDTR) { 187 | Ok(gdtr) => { 188 | assert_eq!(gdtr.0, 0); // GDTR base address 189 | assert_eq!(gdtr.1, 0xffff); // GDTR limit 190 | assert_eq!(gdtr.2, 0); // GDTR attributes 191 | } 192 | Err(error) => panic!("failed to get GDTR register: {:?}", error), 193 | } 194 | 195 | // Local Descriptor-Table Register 196 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_LDTR, 6000).expect("failed to set LDTR register"); 197 | let ldtr = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_LDTR).expect("failed to get LDTR register"); 198 | assert_eq!(ldtr, 6000); 199 | 200 | vm.set_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_LDTR, 0, 0xffff, 0x082).expect("failed to set LDTR register"); 201 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_LDTR, 0).expect("failed to set LDTR register"); 202 | 203 | match vm.get_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_LDTR) { 204 | Ok(ldtr) => { 205 | assert_eq!(ldtr.0, 0); // LDTR base address 206 | assert_eq!(ldtr.1, 0xffff); // LDTR limit 207 | assert_eq!(ldtr.2, 0x082); // LDTR attributes 208 | } 209 | Err(error) => panic!("failed to get LDTR register: {:?}", error), 210 | } 211 | 212 | // Interrupt Descriptor-Table Register 213 | vm.set_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_IDTR, 0, 0xffff, 0).expect("failed to set IDTR register"); 214 | 215 | match vm.get_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_IDTR) { 216 | Ok(idtr) => { 217 | assert_eq!(idtr.0, 0); // IDTR base address 218 | assert_eq!(idtr.1, 0xffff); // IDTR limit 219 | assert_eq!(idtr.2, 0); // IDTR attributes 220 | } 221 | Err(error) => panic!("failed to get IDTR register: {:?}", error), 222 | } 223 | 224 | teardown_vm(testname); 225 | } 226 | 227 | #[test] 228 | fn test_task_register() { 229 | let testname = "test_task_register"; 230 | let vm = setup_vm(testname); 231 | 232 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_TR, 7000).expect("failed to set TR register"); 233 | let tr = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_TR).expect("failed to get TR register"); 234 | assert_eq!(tr, 7000); 235 | 236 | vm.set_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_TR, 0, 0, 0x08b).expect("failed to set TR register"); 237 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_TR, 0).expect("failed to set TR register"); 238 | 239 | match vm.get_desc(TEST_CPUID, vm_reg_name::VM_REG_GUEST_TR) { 240 | Ok(ldtr) => { 241 | assert_eq!(ldtr.0, 0); // TR base address 242 | assert_eq!(ldtr.1, 0); // TR limit 243 | assert_eq!(ldtr.2, 0x08b); // TR attributes 244 | } 245 | Err(error) => panic!("failed to get TR register: {:?}", error), 246 | } 247 | 248 | teardown_vm(testname); 249 | } 250 | 251 | #[test] 252 | fn test_extended_feature_register() { 253 | let testname = "test_extended_feature_register"; 254 | let vm = setup_vm(testname); 255 | 256 | const EFER_LMA: u64 = 0x400; 257 | const EFER_LME: u64 = 0x100; 258 | 259 | let efer_orig = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_EFER).expect("failed to get EFER register"); 260 | 261 | let longmode = efer_orig | EFER_LME | EFER_LMA; 262 | 263 | vm.set_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_EFER, longmode).expect("failed to set EFER register"); 264 | let efer = vm.get_register(TEST_CPUID, vm_reg_name::VM_REG_GUEST_EFER).expect("failed to get EFER register"); 265 | assert_eq!(efer, longmode); 266 | 267 | teardown_vm(testname); 268 | } 269 | --------------------------------------------------------------------------------