aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorjohannst <johannes.stoelp@gmail.com>2021-05-26 00:21:06 +0200
committerjohannst <johannes.stoelp@gmail.com>2021-05-26 00:21:06 +0200
commit786a195f8e81d4f7c0af2a82b9d458361d424a71 (patch)
treec0df3de9a71e3ae1db1cdf4a59cde6d1accadf33
downloadmini-kvm-rs-786a195f8e81d4f7c0af2a82b9d458361d424a71.tar.gz
mini-kvm-rs-786a195f8e81d4f7c0af2a82b9d458361d424a71.zip
minimal KVM abstraction + real mode guest example
-rw-r--r--.gitignore4
-rw-r--r--Cargo.toml9
-rw-r--r--LICENSE21
-rw-r--r--README.md27
-rw-r--r--build.rs25
-rw-r--r--examples/real_mode.rs48
-rw-r--r--guest/Makefile8
-rw-r--r--guest/guest.ld9
-rw-r--r--guest/guest16.S21
-rw-r--r--src/kvm.rs39
-rw-r--r--src/kvm_sys.rs248
-rw-r--r--src/lib.rs145
-rw-r--r--src/vcpu.rs97
-rw-r--r--src/vm.rs45
-rw-r--r--sysdeps/Makefile10
-rw-r--r--sysdeps/kvm.c79
16 files changed, 835 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b68a82a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+Cargo.lock
+sysdeps/kvm
+guest/guest16
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..34e3c7b
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "kvm_rs"
+version = "0.1.0"
+authors = ["johannst <johannes.stoelp@gmail.com>"]
+edition = "2018"
+build = "build.rs"
+
+[dependencies]
+libc = "*"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6144be6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Johannes Stoelp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c0227e6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# kvm-rs
+
+A playground for the [`Kernel Virtual Machine
+(KVM)`](https://www.kernel.org/doc/html/latest/virt/kvm/index.html) in rust to
+learn about KVM and [`rust
+ffi`](https://doc.rust-lang.org/stable/std/ffi/index.html).
+
+The sources are structured as follows:
+- [`src/`](./src) provides a small library as abstraction over the raw [KVM
+ API](https://www.kernel.org/doc/html/latest/virt/kvm/api.html#api-description).
+- [`examples/`](./examples) contains example VMs using the library above.
+- [`guest/`](./guest) contains the guest source code.
+- [`sysdeps/`](./sysdeps) helper to generate some KVM constants from the system
+ header (executed by [build.rs](./build.rs)).
+
+## Real Mode (16bit) example
+
+```bash
+# Once: Build the guest binary image.
+make -C guest
+
+# Run the Real Mode example.
+cargo run --example real_mode
+```
+
+## License
+This project is licensed under the [MIT](LICENSE) license.
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..98d7c88
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,25 @@
+use std::env;
+use std::path::Path;
+use std::io::Write;
+
+fn main() {
+ // Generate KVM constants from the system header <linux/kvm.h>.
+
+ // Only re-run if the generation source file changed.
+ println!("cargo:rerun-if-changed=sysdeps/kvm.c");
+
+ // Input directory.
+ let sysdeps = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("sysdeps");
+
+ // Output file.
+ let kvm_constants = Path::new(&env::var("OUT_DIR").unwrap()).join("kvm_constants.rs");
+
+ // Run make to generate the output file.
+ let o = std::process::Command::new("make")
+ .arg("-C")
+ .arg(sysdeps.to_str().unwrap())
+ .arg(format!("OUT={}", kvm_constants.display()))
+ .output()
+ .unwrap();
+ std::io::stdout().write_all(&o.stdout).unwrap();
+}
diff --git a/examples/real_mode.rs b/examples/real_mode.rs
new file mode 100644
index 0000000..39d1796
--- /dev/null
+++ b/examples/real_mode.rs
@@ -0,0 +1,48 @@
+use kvm_rs::kvm::Kvm;
+use kvm_rs::vcpu::KvmExit;
+use kvm_rs::{PhysAddr, UserMem};
+
+fn main() -> std::io::Result<()> {
+ // Create VM & VCPU.
+ let vm = Kvm::new()?.create_vm()?;
+ let vcpu = vm.create_vpcu(0)?;
+
+ // Map memory for guest VM and initialize with guest image.
+ let mem = UserMem::with_init(0x1000, include_bytes!("../guest/guest16"))?;
+ unsafe {
+ vm.set_user_memory_region(PhysAddr(0x0), &mem)?;
+ }
+
+ // Initialize VPCU registers.
+ let mut regs = vcpu.get_regs()?;
+ regs.rip = 0;
+ regs.rflags.0 = 0x2;
+ vcpu.set_regs(regs)?;
+
+ // Initialize VPCU special registers.
+ let mut sregs = vcpu.get_sregs()?;
+ sregs.cs.base = 0;
+ sregs.cs.selector = 0;
+ vcpu.set_sregs(sregs)?;
+
+ // Run VCPU until `hlt` instruction.
+ while let Ok(exit) = vcpu.run() {
+ match exit {
+ KvmExit::Halt => break,
+ KvmExit::IoOut(_port, data) => {
+ let s = std::str::from_utf8(data).unwrap();
+ print!("{}", s);
+ }
+ KvmExit::MmioWrite(addr, data) => {
+ println!(
+ "MMIO_WRITE: addr={:#x} len={} data={:#x?}",
+ addr,
+ data.len(),
+ data
+ );
+ }
+ };
+ }
+
+ Ok(())
+}
diff --git a/guest/Makefile b/guest/Makefile
new file mode 100644
index 0000000..e3f1e1b
--- /dev/null
+++ b/guest/Makefile
@@ -0,0 +1,8 @@
+guest16: guest.ld guest16.S
+ $(CC) $(CFLAGS) -m16 -o $@ -nostdlib -ffreestanding -Wpedantic -Wall -Wextra -Werror -T guest.ld guest16.S
+
+disasm: guest16
+ objdump -D -b binary -m i8086 -M intel $^
+
+clean:
+ $(RM) guest16
diff --git a/guest/guest.ld b/guest/guest.ld
new file mode 100644
index 0000000..5c81da9
--- /dev/null
+++ b/guest/guest.ld
@@ -0,0 +1,9 @@
+OUTPUT_FORMAT(binary)
+
+SECTIONS {
+ .boot : { *(.boot) }
+ .text : { *(.text) }
+ .data : { *(.data) }
+ .rodata : { *(.rodata) }
+ /DISCARD/ : { *(.*) }
+}
diff --git a/guest/guest16.S b/guest/guest16.S
new file mode 100644
index 0000000..7f0be0e
--- /dev/null
+++ b/guest/guest16.S
@@ -0,0 +1,21 @@
+.code16
+.intel_syntax noprefix
+
+.section .boot, "ax", @progbits
+ // Trigger `KVM_EXIT_IO:KVM_EXIT_IO_OUT` by writing string to output port.
+ mov dx, 0x1000 // Output port.
+ lea si, [msg] // Address of string.
+ mov cx, [msg_len] // Len of string.
+ rep outsb dx, ds:[si] // Write out string bytes.
+
+ // Trigger `KVM_EXIT_MMIO` by writing to non mapped physical address.
+ mov byte ptr ds:[0x2000], 0xaa
+
+ // Trigger `KVM_EXIT_HLT`.
+ hlt
+
+.section .rodata, "a", @progbits
+msg:
+ .asciz "Hello from Real Mode!\n"
+msg_len:
+ .byte .-msg
diff --git a/src/kvm.rs b/src/kvm.rs
new file mode 100644
index 0000000..3522adc
--- /dev/null
+++ b/src/kvm.rs
@@ -0,0 +1,39 @@
+use std::fs;
+use std::io;
+use std::os::unix::io::FromRawFd;
+
+use crate::{libcret, ioctl, kvm_sys};
+use crate::vm::Vm;
+
+pub struct Kvm {
+ kvm: fs::File,
+}
+
+impl Kvm {
+ pub fn new() -> io::Result<Kvm> {
+ let kvm = libcret(unsafe {
+ libc::open("/dev/kvm\0".as_ptr().cast(), libc::O_RDWR | libc::O_CLOEXEC)
+ })
+ .map(|fd| unsafe { fs::File::from_raw_fd(fd) })?;
+
+ assert_eq!(
+ kvm_sys::KVM_API_VERSION,
+ ioctl(&kvm, kvm_sys::KVM_GET_API_VERSION, 0)?
+ );
+
+ Ok(Kvm { kvm })
+ }
+
+ fn get_vpcu_mmap_size(&self) -> io::Result<usize> {
+ ioctl(&self.kvm, kvm_sys::KVM_GET_VCPU_MMAP_SIZE, 0).map(|size| size as usize)
+ }
+
+ pub fn create_vm(&self) -> io::Result<Vm> {
+ let vm = ioctl(&self.kvm, kvm_sys::KVM_CREATE_VM, 0 /* machine id */)
+ .map(|fd| unsafe { fs::File::from_raw_fd(fd) })?;
+
+ let vcpu_mmap_size = self.get_vpcu_mmap_size()?;
+
+ Ok(Vm::new(vm, vcpu_mmap_size))
+ }
+}
diff --git a/src/kvm_sys.rs b/src/kvm_sys.rs
new file mode 100644
index 0000000..8d5c85b
--- /dev/null
+++ b/src/kvm_sys.rs
@@ -0,0 +1,248 @@
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+// Generated by `build.rs`.
+include!(concat!(env!("OUT_DIR"), "/kvm_constants.rs"));
+
+use std::fmt;
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Rflags(pub u64);
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct kvm_regs {
+ pub rax: u64,
+ pub rbx: u64,
+ pub rcx: u64,
+ pub rdx: u64,
+ pub rsi: u64,
+ pub rdi: u64,
+ pub rsp: u64,
+ pub rbp: u64,
+ pub r8: u64,
+ pub r9: u64,
+ pub r10: u64,
+ pub r11: u64,
+ pub r12: u64,
+ pub r13: u64,
+ pub r14: u64,
+ pub r15: u64,
+ pub rip: u64,
+ pub rflags: Rflags,
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct kvm_segment {
+ pub base: u64,
+ pub limit: u32,
+ pub selector: u16,
+ pub type_: u8,
+ pub present: u8,
+ pub dpl: u8,
+ pub db: u8,
+ pub s: u8,
+ pub l: u8,
+ pub g: u8,
+ pub avl: u8,
+ unusable: u8,
+ _padding: u8,
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct kvm_dtable {
+ pub base: u64,
+ pub limit: u16,
+ _padding: [u16; 3],
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct kvm_sregs {
+ pub cs: kvm_segment,
+ pub ds: kvm_segment,
+ pub es: kvm_segment,
+ pub fs: kvm_segment,
+ pub gs: kvm_segment,
+ pub ss: kvm_segment,
+ pub tr: kvm_segment,
+ pub ldt: kvm_segment,
+ pub gdt: kvm_dtable,
+ pub idt: kvm_dtable,
+ pub cr0: u64,
+ cr2: u64,
+ pub cr3: u64,
+ pub cr4: u64,
+ pub cr8: u64,
+ pub effer: u64,
+ pub apic_base: u64,
+ pub interrupt_bitmap: [u64; 4],
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub(crate) struct kvm_userspace_memory_region {
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
+ pub userspace_addr: u64,
+}
+
+#[repr(C)]
+pub(crate) struct kvm_run {
+ request_interrupt_window: u8,
+ immediate_exit: u8,
+ padding1: [u8; 6],
+ pub exit_reason: u32,
+ ready_for_interrupt_injection: u8,
+ if_flag: u8,
+ flags: u16,
+ cr8: u64,
+ apic_base: u64,
+ pub inner: kvm_run_union,
+ kvm_valid_regs: u64,
+ kvm_dirty_regs: u64,
+ s: kvm_run_union_s,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct kvm_run_io {
+ pub direction: u8,
+ pub size: u8,
+ pub port: u16,
+ pub count: u32,
+ pub data_offset: u64,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct kvm_run_mmio {
+ pub phys_addr: u64,
+ pub data: [u8; 8],
+ pub len: u32,
+ pub is_write: u8,
+}
+
+// Only add the union fields used here.
+#[repr(C)]
+pub(crate) union kvm_run_union {
+ pub io: kvm_run_io,
+ pub mmio: kvm_run_mmio,
+ padding: [u8; 256],
+}
+
+// Only add the union fields used here.
+#[repr(C)]
+union kvm_run_union_s {
+ padding: [u8; 2048],
+}
+
+// {{{ Display : Rflags
+
+impl fmt::Display for Rflags {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let IF = || (self.0 >> 9) & 0b1;
+ let IOPL = || (self.0 >> 12) & 0b11;
+ let AC = || (self.0 >> 19) & 0b1;
+ write!(f, "AC({}) IOPL({}) IF({})", AC(), IOPL(), IF())
+ }
+}
+
+// }}}
+// {{{ Display : kvm_regs
+
+impl fmt::Display for kvm_regs {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "rax: {:#018x} rbx: {:#018x} rcx: {:#018x} rdx: {:#018x}\n\
+ rsi: {:#018x} rdi: {:#018x}\n\
+ r8 : {:#018x} r9 : {:#018x} r10: {:#018x} r11: {:#018x}\n\
+ r12: {:#018x} r13: {:#018x} r14: {:#018x} r15: {:#018x}\n\
+ rsp: {:#018x} rbp: {:#018x}\n\
+ rip: {:#018x} rflags: {:#018x} [{}]",
+ self.rax,
+ self.rbx,
+ self.rcx,
+ self.rdx,
+ self.rsi,
+ self.rdi,
+ self.r8,
+ self.r9,
+ self.r10,
+ self.r11,
+ self.r12,
+ self.r13,
+ self.r14,
+ self.r15,
+ self.rsp,
+ self.rbp,
+ self.rip,
+ self.rflags.0,
+ self.rflags
+ )
+ }
+}
+
+// }}}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::mem;
+
+ #[test]
+ fn check_kvm_regs() {
+ assert_eq!(mem::size_of::<kvm_regs>(), TEST_KVM_REGS_SIZE);
+ assert_eq!(mem::align_of::<kvm_regs>(), TEST_KVM_REGS_ALIGN);
+ }
+
+ #[test]
+ fn check_kvm_segment() {
+ assert_eq!(mem::size_of::<kvm_segment>(), TEST_KVM_SEGMENT_SIZE);
+ assert_eq!(mem::align_of::<kvm_segment>(), TEST_KVM_SEGMENT_ALIGN);
+ }
+
+ #[test]
+ fn check_kvm_dtable() {
+ assert_eq!(mem::size_of::<kvm_dtable>(), TEST_KVM_DTABLE_SIZE);
+ assert_eq!(mem::align_of::<kvm_dtable>(), TEST_KVM_DTABLE_ALIGN);
+ }
+
+ #[test]
+ fn check_kvm_sregs() {
+ assert_eq!(mem::size_of::<kvm_sregs>(), TEST_KVM_SREGS_SIZE);
+ assert_eq!(mem::align_of::<kvm_sregs>(), TEST_KVM_SREGS_ALIGN);
+ assert_eq!(
+ mem::size_of_val(&kvm_sregs::default().interrupt_bitmap),
+ TEST_KVM_SREGS_INTERRTUP_BITMAP_SIZE
+ );
+ }
+
+ #[test]
+ fn check_kvm_userspace_memory_region() {
+ assert_eq!(
+ mem::size_of::<kvm_userspace_memory_region>(),
+ TEST_KVM_USERSPACE_MEMORY_REGION_SIZE
+ );
+ assert_eq!(
+ mem::align_of::<kvm_userspace_memory_region>(),
+ TEST_KVM_USERSPACE_MEMORY_REGION_ALIGN
+ );
+ }
+
+ #[test]
+ fn check_kvm_run() {
+ assert_eq!(mem::size_of::<kvm_run>(), TEST_KVM_RUN_SIZE);
+ assert_eq!(mem::align_of::<kvm_run>(), TEST_KVM_RUN_ALIGN);
+ assert_eq!(mem::size_of::<kvm_run_io>(), TEST_KVM_RUN_IO_SIZE);
+ assert_eq!(mem::size_of::<kvm_run_mmio>(), TEST_KVM_RUN_MMIO_SIZE);
+ assert_eq!(mem::size_of::<kvm_run_union_s>(), TEST_KVM_RUN_UNION_S_SIZE);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..6793272
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,145 @@
+use std::convert::{AsMut, AsRef};
+use std::io;
+use std::ops;
+use std::os::unix::io::AsRawFd;
+
+pub mod kvm;
+pub mod kvm_sys;
+pub mod vcpu;
+pub mod vm;
+
+/// Strong type representing physical addresses.
+pub struct PhysAddr(pub u64);
+
+/// Helper to turn libc return values into an [io::Result](std::io::Result). Returns
+/// [`Error::last_os_error`](std::io::Error::last_os_error) if `ret < 0`.
+fn libcret(ret: libc::c_int) -> io::Result<libc::c_int> {
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(ret)
+ }
+}
+
+/// Wrapper of `libc::ioctl` for KVM ioctls with one argument and returning an
+/// [`io::Result`](std::io::Result).
+fn ioctl<F: AsRawFd>(fd: &F, cmd: u64, arg: u64) -> io::Result<libc::c_int> {
+ libcret(unsafe { libc::ioctl(fd.as_raw_fd(), cmd, arg) })
+}
+
+/// Wrapper to safely allocate memory for guest VMs.
+///
+/// The underlying memory is freed automatically once the `UserMem` instance is dropped.
+///
+/// Memory can be mapped into a guest VM with
+/// [`Vm::set_user_memory_region`](crate::vm::Vm::set_user_memory_region).
+pub struct UserMem {
+ ptr: *mut u8,
+ len: usize,
+}
+
+impl UserMem {
+ /// Allocate a zero-initialized memory region of `len` bytes.
+ pub fn new(len: usize) -> io::Result<UserMem> {
+ let ptr = unsafe {
+ libc::mmap(
+ std::ptr::null_mut(),
+ len,
+ libc::PROT_READ | libc::PROT_WRITE,
+ libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
+ -1,
+ 0,
+ )
+ };
+
+ if ptr == libc::MAP_FAILED {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(UserMem {
+ ptr: ptr.cast(),
+ len,
+ })
+ }
+ }
+
+ /// Allocate a zero-initialized memory region of `len` bytes and initialize the first bytes
+ /// with `init_len`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `init_from` is larger than the memory size `len`.
+ pub fn with_init(len: usize, init_from: &[u8]) -> io::Result<UserMem> {
+ assert!(len >= init_from.len());
+
+ let mut m = UserMem::new(len)?;
+ m.as_mut()[..init_from.len()].copy_from_slice(init_from);
+ Ok(m)
+ }
+}
+
+impl ops::Drop for UserMem {
+ /// Free underlying memory.
+ fn drop(&mut self) {
+ unsafe { libc::munmap(self.ptr.cast(), self.len) };
+ }
+}
+
+impl AsRef<[u8]> for UserMem {
+ fn as_ref(&self) -> &[u8] {
+ unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
+ }
+}
+
+impl AsMut<[u8]> for UserMem {
+ fn as_mut(&mut self) -> &mut [u8] {
+ unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
+ }
+}
+
+/// Internal wrapper to automatically `mmap` and `munmap` the the [`struct kvm_run`][kvm_run]
+/// for a given VPCU.
+///
+/// [kvm_run]: https://www.kernel.org/doc/html/latest/virt/kvm/api.html#the-kvm-run-structure
+struct KvmRun {
+ ptr: *mut kvm_sys::kvm_run,
+ len: usize,
+}
+
+impl KvmRun {
+ /// Mmap the `struct kvm_run` for a given `VCPU` referenced by the argument file descriptor
+ /// `vcpu`.
+ fn new<F: AsRawFd>(vcpu: &F, len: usize) -> io::Result<KvmRun> {
+ let ptr = unsafe {
+ libc::mmap(
+ std::ptr::null_mut(),
+ len,
+ libc::PROT_READ,
+ libc::MAP_SHARED,
+ vcpu.as_raw_fd(),
+ 0,
+ )
+ };
+
+ if ptr == libc::MAP_FAILED {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(KvmRun {
+ ptr: ptr.cast(),
+ len,
+ })
+ }
+ }
+}
+
+impl ops::Drop for KvmRun {
+ /// Munmap the mmaped `struct kvm_run`.
+ fn drop(&mut self) {
+ unsafe { libc::munmap(self.ptr.cast(), self.len) };
+ }
+}
+
+impl AsRef<kvm_sys::kvm_run> for KvmRun {
+ fn as_ref(&self) -> &kvm_sys::kvm_run {
+ unsafe { &(*self.ptr) }
+ }
+}
diff --git a/src/vcpu.rs b/src/vcpu.rs
new file mode 100644
index 0000000..988bedc
--- /dev/null
+++ b/src/vcpu.rs
@@ -0,0 +1,97 @@
+use std::fs;
+use std::io;
+
+use crate::{ioctl, kvm_sys, KvmRun};
+
+pub enum KvmExit<'cpu> {
+ Halt,
+ IoOut(u16, &'cpu [u8]),
+ MmioWrite(u64, &'cpu [u8]),
+}
+
+pub struct Vcpu {
+ vcpu: fs::File,
+ kvm_run: KvmRun,
+}
+
+impl Vcpu {
+ pub(crate) fn new(vcpu: fs::File, kvm_run: KvmRun) -> Vcpu {
+ Vcpu { vcpu, kvm_run }
+ }
+
+ pub fn get_regs(&self) -> io::Result<kvm_sys::kvm_regs> {
+ let mut regs = kvm_sys::kvm_regs::default();
+ ioctl(
+ &self.vcpu,
+ kvm_sys::KVM_GET_REGS,
+ &mut regs as *mut _ as u64,
+ )?;
+ Ok(regs)
+ }
+
+ pub fn set_regs(&self, regs: kvm_sys::kvm_regs) -> io::Result<()> {
+ ioctl(&self.vcpu, kvm_sys::KVM_SET_REGS, &regs as *const _ as u64).map(|_| ())
+ }
+
+ pub fn get_sregs(&self) -> io::Result<kvm_sys::kvm_sregs> {
+ let mut sregs = kvm_sys::kvm_sregs::default();
+ ioctl(
+ &self.vcpu,
+ kvm_sys::KVM_GET_SREGS,
+ &mut sregs as *mut _ as u64,
+ )?;
+ Ok(sregs)
+ }
+
+ pub fn set_sregs(&self, sregs: kvm_sys::kvm_sregs) -> io::Result<()> {
+ ioctl(
+ &self.vcpu,
+ kvm_sys::KVM_SET_SREGS,
+ &sregs as *const _ as u64,
+ )
+ .map(|_| ())
+ }
+
+ pub fn run(&self) -> io::Result<KvmExit> {
+ ioctl(&self.vcpu, kvm_sys::KVM_RUN, 0)?;
+
+ let kvm_run = self.kvm_run.as_ref();
+
+ match kvm_run.exit_reason as u64 {
+ kvm_sys::KVM_EXIT_HLT => Ok(KvmExit::Halt),
+ kvm_sys::KVM_EXIT_IO => {
+ // Safe to use union `io` field, as Kernel instructed us to.
+ let io = unsafe { kvm_run.inner.io };
+
+ let kvm_run_ptr = kvm_run as *const kvm_sys::kvm_run as *const u8;
+
+ // Create IO buffer located at `kvm_run + io.offset`.
+ let data = unsafe {
+ std::slice::from_raw_parts(
+ kvm_run_ptr.offset(io.data_offset as isize),
+ io.count /* num blocks */ as usize * io.size /* bytes per block */ as usize,
+ )
+ };
+
+ match io.direction as u64 {
+ kvm_sys::KVM_EXIT_IO_IN => todo!("KVM_EXIT_IO_IN not implemented!"),
+ kvm_sys::KVM_EXIT_IO_OUT => Ok(KvmExit::IoOut(io.port, data)),
+ _ => unreachable!(),
+ }
+ }
+ kvm_sys::KVM_EXIT_MMIO => {
+ // Safe to use union `mmio` filed, as Kernel instructed us to.
+ let mmio = unsafe { &kvm_run.inner.mmio };
+ let len = mmio.len as usize;
+
+ // Only support write at the moment.
+ assert_ne!(0, mmio.is_write);
+
+ Ok(KvmExit::MmioWrite(mmio.phys_addr, &mmio.data[..len]))
+ }
+ r @ _ => {
+ todo!("KVM_EXIT_... (exit_reason={}) not implemented!", r)
+ }
+ }
+ }
+}
diff --git a/src/vm.rs b/src/vm.rs
new file mode 100644
index 0000000..6f8a355
--- /dev/null
+++ b/src/vm.rs
@@ -0,0 +1,45 @@
+use std::fs;
+use std::io;
+use std::os::unix::io::FromRawFd;
+
+use crate::vcpu::Vcpu;
+use crate::{ioctl, kvm_sys, KvmRun, PhysAddr, UserMem};
+
+pub struct Vm {
+ vm: fs::File,
+ vcpu_mmap_size: usize,
+}
+
+impl Vm {
+ pub(crate) fn new(vm: fs::File, vcpu_mmap_size: usize) -> Vm {
+ Vm { vm, vcpu_mmap_size }
+ }
+
+ pub unsafe fn set_user_memory_region(
+ &self,
+ phys_addr: PhysAddr,
+ mem: &UserMem,
+ ) -> io::Result<()> {
+ // Create guest physical memory mapping for `slot : 0` at guest `phys addr : 0`.
+ let mut kvm_mem = kvm_sys::kvm_userspace_memory_region::default();
+ kvm_mem.userspace_addr = mem.ptr as u64;
+ kvm_mem.memory_size = mem.len as u64;
+ kvm_mem.guest_phys_addr = phys_addr.0;
+
+ ioctl(
+ &self.vm,
+ kvm_sys::KVM_SET_USER_MEMORY_REGION,
+ &kvm_mem as *const _ as u64,
+ )
+ .map(|_| ())
+ }
+
+ pub fn create_vpcu(&self, id: u64) -> io::Result<Vcpu> {
+ let vcpu = ioctl(&self.vm, kvm_sys::KVM_CREATE_VCPU, id)
+ .map(|fd| unsafe { fs::File::from_raw_fd(fd) })?;
+
+ let kvm_run = KvmRun::new(&vcpu, self.vcpu_mmap_size)?;
+
+ Ok(Vcpu::new(vcpu, kvm_run))
+ }
+}
diff --git a/sysdeps/Makefile b/sysdeps/Makefile
new file mode 100644
index 0000000..092047e
--- /dev/null
+++ b/sysdeps/Makefile
@@ -0,0 +1,10 @@
+OUT ?= /dev/stdout
+
+gen: kvm
+ ./kvm > $(OUT)
+
+kvm: kvm.c
+ $(CC) $(CFLAGS) -Wpedantic -Wall -Wextra -Werror -o $@ $^
+
+clean:
+ $(RM) kvm
diff --git a/sysdeps/kvm.c b/sysdeps/kvm.c
new file mode 100644
index 0000000..1665349
--- /dev/null
+++ b/sysdeps/kvm.c
@@ -0,0 +1,79 @@
+#include <linux/kvm.h>
+
+#include <stdio.h>
+#include <stdalign.h> // alignof operator
+#include <stddef.h> // offsetof operator
+
+int main() {
+ /* Global constants */
+
+ printf("pub(crate) const KVM_API_VERSION : i32 = 0x%x;\n", KVM_API_VERSION);
+
+ /* ioctl's for /dev/kvm */
+
+ // param: none
+ // ret : constant KVM_API_VERSION (=12)
+ printf("pub(crate) const KVM_GET_API_VERSION : u64 = 0x%x;\n", KVM_GET_API_VERSION);
+ // param: machine type identifier (most cases just 0)
+ // ret : VM fd
+ printf("pub(crate) const KVM_CREATE_VM : u64 = 0x%x;\n", KVM_CREATE_VM);
+ // param: none
+ // ret : size of vcpu mmap region in bytes
+ printf("pub(crate) const KVM_GET_VCPU_MMAP_SIZE : u64 = 0x%x;\n", KVM_GET_VCPU_MMAP_SIZE);
+
+ /* ioctl's for VM fd */
+
+ // param: vpcu id
+ // ret : VCPU fd
+ printf("pub(crate) const KVM_CREATE_VCPU : u64 = 0x%x;\n", KVM_CREATE_VCPU);
+ // param: struct kvm_userspace_memory_region
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_SET_USER_MEMORY_REGION : u64 = 0x%lx;\n", KVM_SET_USER_MEMORY_REGION);
+
+ /* ioctl's for VCPU fd */
+
+ // param: none
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_RUN : u64 = 0x%x;\n", KVM_RUN);
+ // param: struct kvm_regs
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_GET_REGS : u64 = 0x%lx;\n", KVM_GET_REGS);
+ // param: struct kvm_regs
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_SET_REGS : u64 = 0x%lx;\n", KVM_SET_REGS);
+ // param: struct kvm_sregs
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_GET_SREGS : u64 = 0x%lx;\n", KVM_GET_SREGS);
+ // param: struct kvm_sregs
+ // ret : 0 success, -1 error
+ printf("pub(crate) const KVM_SET_SREGS : u64 = 0x%lx;\n", KVM_SET_SREGS);
+
+ /* struct kvm_run constants */
+
+ printf("pub(crate) const KVM_EXIT_HLT : u64 = 0x%x;\n", KVM_EXIT_HLT);
+ printf("pub(crate) const KVM_EXIT_IO : u64 = 0x%x;\n", KVM_EXIT_IO);
+ printf("pub(crate) const KVM_EXIT_IO_IN : u64 = 0x%x;\n", KVM_EXIT_IO_IN);
+ printf("pub(crate) const KVM_EXIT_IO_OUT : u64 = 0x%x;\n", KVM_EXIT_IO_OUT);
+ printf("pub(crate) const KVM_EXIT_MMIO : u64 = 0x%x;\n", KVM_EXIT_MMIO);
+
+ /* Testing constants */
+
+ printf("#[cfg(test)] const TEST_KVM_REGS_SIZE : usize = %ld;\n", sizeof(struct kvm_regs));
+ printf("#[cfg(test)] const TEST_KVM_REGS_ALIGN : usize = %ld;\n", alignof(struct kvm_regs));
+ printf("#[cfg(test)] const TEST_KVM_SREGS_SIZE : usize = %ld;\n", sizeof(struct kvm_sregs));
+ printf("#[cfg(test)] const TEST_KVM_SREGS_ALIGN : usize = %ld;\n", alignof(struct kvm_sregs));
+ printf("#[cfg(test)] const TEST_KVM_SREGS_INTERRTUP_BITMAP_SIZE : usize = %ld;\n", sizeof(((struct kvm_sregs*)0)->interrupt_bitmap));
+ printf("#[cfg(test)] const TEST_KVM_SEGMENT_SIZE : usize = %ld;\n", sizeof(struct kvm_segment));
+ printf("#[cfg(test)] const TEST_KVM_SEGMENT_ALIGN : usize = %ld;\n", alignof(struct kvm_segment));
+ printf("#[cfg(test)] const TEST_KVM_DTABLE_SIZE : usize = %ld;\n", sizeof(struct kvm_dtable));
+ printf("#[cfg(test)] const TEST_KVM_DTABLE_ALIGN : usize = %ld;\n", alignof(struct kvm_dtable));
+ printf("#[cfg(test)] const TEST_KVM_USERSPACE_MEMORY_REGION_SIZE : usize = %ld;\n", sizeof(struct kvm_userspace_memory_region));
+ printf("#[cfg(test)] const TEST_KVM_USERSPACE_MEMORY_REGION_ALIGN : usize = %ld;\n", alignof(struct kvm_userspace_memory_region));
+ printf("#[cfg(test)] const TEST_KVM_RUN_SIZE : usize = %ld;\n", sizeof(struct kvm_run));
+ printf("#[cfg(test)] const TEST_KVM_RUN_ALIGN : usize = %ld;\n", alignof(struct kvm_run));
+ printf("#[cfg(test)] const TEST_KVM_RUN_IO_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->io));
+ printf("#[cfg(test)] const TEST_KVM_RUN_MMIO_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->mmio));
+ printf("#[cfg(test)] const TEST_KVM_RUN_UNION_S_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->s));
+
+ return 0;
+}