diff --git a/.cargo/config b/.cargo/config index e3ca9c5..27e0527 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,9 +1,13 @@ [build] target = "x86_64-unknown-uefi" -[target.x86_64-unknown-uefi] -rustflags = ["-Clink-args= /subsystem:EFI_RUNTIME_DRIVER"] - [unstable] build-std = ["core", "compiler_builtins"] build-std-features = ["compiler-builtins-mem"] + +[target.x86_64-unknown-uefi.uefi] +rustflags = "-Clink-args=' /subsystem:EFI_RUNTIME_DRIVER'" + +#[target.x86_64-unknown-uefi.rustyvctl] +#rustflags = ["-Clink-args= /subsystem:EFI_APPLICATION"] + diff --git a/Cargo.toml b/Cargo.toml index b20b333..806b13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,10 @@ members = [ "hypervisor", + "hypervisor_abi", "uefi", "pcuart", "linux", "dmesg_logger", + "rustyvctl", ] diff --git a/README.md b/README.md index 1d91c70..25f9c35 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,13 @@ rustup target install x86_64-unknown-uefi Once you have the right version of rust installed, building is straightforward: ``` -cargo build --target x86_64-unknown-uefi +sh build.sh ``` ## Launching from a UEFI shell First, build the project, and copy the hypervisor from -`target/x86_64-unknown-uefi/debug/uefi.efi` onto a USB stick. +`target/x86_64-unknown-uefi/debug/rustyvisor.efi` onto a USB stick. Unmount the USB stick from your development device and insert it into your test @@ -75,11 +75,11 @@ Press ESC in 1 seconds to skip startup.nsh or any other key to continue. Shell> fs0: FS0:\> dir Directory of: FS0:\ -06/03/2021 23:33 342,016 uefi.efi +06/03/2021 23:33 342,016 rustyvisor.efi 06/03/2021 23:42 10,383 NvVars 2 File(s) 433,807 bytes 0 Dir(s) -FS0:\> load .\uefi.efi +FS0:\> load .\rustyvisor.efi FS0:\> ``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..342dd2d --- /dev/null +++ b/build.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -x +set -e + +if [ "$1" = "clean" ] ; then + cd uefi || exit 255 + cargo clean + cd .. + + cd rustyvctl || exit 255 + cargo clean + cd .. + + cd linux || exit 255 + make clean + cd .. + + exit 0 +fi + +cd uefi || exit 255 +cargo build +cd .. + +cd rustyvctl || exit 255 +cargo build +cd .. + +cd linux || exit 255 +make +cd .. diff --git a/dmesg_logger/src/lib.rs b/dmesg_logger/src/lib.rs index 52294f9..dcc8a34 100644 --- a/dmesg_logger/src/lib.rs +++ b/dmesg_logger/src/lib.rs @@ -1,4 +1,9 @@ #![no_std] +#![warn(missing_docs)] + +//! A logging facade for writing logging information to the Linux Kernel System +//! Log. + use log; use core::fmt; @@ -8,6 +13,7 @@ extern "C" { fn printk(fmt: *const u8, ...) -> i32; } +/// A logger which writes to the Linux Kernel System Log. pub struct DMesgLogger {} struct PrintK {} @@ -31,6 +37,9 @@ impl fmt::Write for PrintK { } impl DMesgLogger { + /// A function similar to core::fmt::Write, except that self is not + /// mutable, so we can use it without doing any locking. We let Linux + /// handle the locking for us. pub fn write_fmt(&self, args: core::fmt::Arguments) { let mut printk_obj = PrintK {}; let _ = write!(printk_obj, "{}\r\n", args); diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 5e079b4..6e234b9 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" spin = { version = "0.9" } log = { default-features = false, version = "0.4" } pcuart = { path= "../pcuart"} +hypervisor_abi = { path= "../hypervisor_abi"} dmesg_logger = { path = "../dmesg_logger" } #x86 = "0.39.0" x86 = { git = "https://github.com/iankronquist/rust-x86" } diff --git a/hypervisor/src/hypercall_handler.rs b/hypervisor/src/hypercall_handler.rs new file mode 100644 index 0000000..44c4aea --- /dev/null +++ b/hypervisor/src/hypercall_handler.rs @@ -0,0 +1,48 @@ +use crate::register_state::GeneralPurposeRegisterState; + +const HYPERVISOR_VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn parse_version(version_string: &str) -> [u32; 3] { + let mut index = 0; + let mut version: [u32; 3] = [0, 0, 0]; + for c in version_string.chars() { + if index > version.len() { + break; + } + + if let Some(digit) = c.to_digit(10) { + version[index] *= 10; + version[index] += digit; + } else { + index += 1; + continue; + } + } + + version +} + +/// Handle a hypercall. +/// Expects gprs.rax to hold hypercall::HYPERCALL_MAGIC and gprs.rcx to hold a +/// valid hypercall reason. +pub fn handle_hypercall(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> { + assert_eq!(hypervisor_abi::HYPERCALL_MAGIC, gprs.rax as u32); + + let reason = gprs.rcx as u32; + match reason { + hypervisor_abi::HYPERCALL_REASON_VERSION => { + let version = parse_version(&HYPERVISOR_VERSION); + gprs.rax = u64::from(version[0]); + gprs.rbx = u64::from(version[1]); + gprs.rcx = u64::from(version[2]); + gprs.rdx = 0; // Reserved 0 + } + _ => { + gprs.rax = 0; + gprs.rbx = 0; + gprs.rcx = 0; + gprs.rdx = 0; + } + } + Ok(()) +} diff --git a/hypervisor/src/interrupt_controller.rs b/hypervisor/src/interrupt_controller.rs index 346b169..47ed9a8 100644 --- a/hypervisor/src/interrupt_controller.rs +++ b/hypervisor/src/interrupt_controller.rs @@ -11,6 +11,11 @@ use crate::{ const VM_PREEMPTION_TIMER_VALUE: u64 = 0xffff; const INTERRUPT_COUNT: usize = 256; + +/// A Virtualized Interrupt Controller +/// Caches various interrupt information. +/// Should be initialized as all zeroes. +/// Each core should have their own VirtualLocalInterruptController. pub struct VirtualLocalInterruptController { total_interrupts_count: usize, delayed_delivery_interrupts: [usize; INTERRUPT_COUNT], @@ -118,6 +123,10 @@ fn vmx_deconfigure_interrupt_window_exiting() -> Result<(), x86::vmx::VmFail> { vmwrite(VmcsField::CpuBasedVmExecControl, cpu_based_controls) } +/// Call this function when an external interrupt is received. +/// If the guest is interruptable, inject the interrupt into the guest. +/// Otherwise, cache it for later delivery and configure the guest to wake up +/// later for interrupt delivery. pub fn received_external_interrupt() -> Result<(), x86::vmx::VmFail> { let interrupt_info = vmread(VmcsField::VmExitIntrInfo)?; let interrupt_number = interrupt_info & 0xff; @@ -134,6 +143,17 @@ pub fn received_external_interrupt() -> Result<(), x86::vmx::VmFail> { } } +/// Call when the guest preemption timer fires. +/// If the guest does not support interrupt window exiting, and there is an +/// outstanding interrupt cached, and the guest is interruptable, +/// an interrupt will be injected into the guest. +/// +/// If the interrupt controller requested the timer be enabled and the timer is +/// not being used elsewhere in the hypervisor, and there are no outstanding +/// interrupts, the timer will be disabled to allow the guest to run unimpeded. +/// +/// This function only does work if the guest does not support interrupt window +/// exiting. pub fn received_preemption_timer() -> Result<(), x86::vmx::VmFail> { let local_interrupt_controller = get_local_interrupt_controller(); if local_interrupt_controller.requested_poll_of_interrupts_on_next_preemption_timer @@ -157,6 +177,9 @@ pub fn received_preemption_timer() -> Result<(), x86::vmx::VmFail> { Ok(()) } +/// If interrupt window exiting is supported by the guest, then this function +/// should be called when an interrupt window exit occurs. This function will +/// then inject any outstanding interrupts into the guest. pub fn received_interrupt_window_exit() -> Result<(), x86::vmx::VmFail> { let local_interrupt_controller = get_local_interrupt_controller(); assert!(vmx_is_guest_interruptable()); diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 9f60ad8..55cf0e7 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -2,6 +2,7 @@ #![feature(asm)] #![feature(lang_items)] #![allow(unknown_lints)] +#![warn(missing_docs)] //! A library implementing a mostly-passthrough hypervisor. //! A mostly passthrough hypervisor mostly virtualizes the guest and does very @@ -25,8 +26,10 @@ //! 2. Once globally, call [rustyvisor_unload](fn.rustyvisor_unload.html) use ::log::{error, info, trace, LevelFilter}; +extern crate hypervisor_abi; mod debug; +mod hypercall_handler; pub mod interrupt_controller; mod interrupts; mod isr; diff --git a/hypervisor/src/register_state.rs b/hypervisor/src/register_state.rs index 856f44a..957381d 100644 --- a/hypervisor/src/register_state.rs +++ b/hypervisor/src/register_state.rs @@ -7,21 +7,21 @@ #[derive(Debug)] #[repr(C)] pub struct GeneralPurposeRegisterState { - r15: u64, - r14: u64, - r13: u64, - r12: u64, - r11: u64, - r10: u64, - r9: u64, - r8: u64, - rdi: u64, - rsi: u64, - rbp: u64, - rdx: u64, - rcx: u64, - rbx: u64, - rax: u64, + pub r15: u64, + pub r14: u64, + pub r13: u64, + pub r12: u64, + pub r11: u64, + pub r10: u64, + pub r9: u64, + pub r8: u64, + pub rdi: u64, + pub rsi: u64, + pub rbp: u64, + pub rdx: u64, + pub rcx: u64, + pub rbx: u64, + pub rax: u64, } impl GeneralPurposeRegisterState { diff --git a/hypervisor/src/segmentation.rs b/hypervisor/src/segmentation.rs index 4b8e136..6323487 100644 --- a/hypervisor/src/segmentation.rs +++ b/hypervisor/src/segmentation.rs @@ -4,23 +4,36 @@ use log::trace; use x86; const GDT_ENTRY_ACCESS_PRESENT: u8 = 1 << 7; -// Table 24-2 ch 24-4 vol 3c +// See Intel manual Table 24-2 ch 24-4 vol 3c const VMX_INFO_SEGMENT_UNUSABLE: u32 = 1 << 16; +/// GDT entries are packed in a complicated way meant to be backwards +/// compatible since the days of the i286. This represents the component parts +///of a GDT entry unpacked into a format we can feed into various host and +/// guest VMCS entries. #[derive(Default, Debug)] pub struct UnpackedGdtEntry { + /// The base of the segment. pub base: u64, + /// The limit of the segment. pub limit: u64, + /// The access rights of the segment. pub access_rights: u32, + /// The segment selector. pub selector: u16, } impl UnpackedGdtEntry { + /// Checks to see if the GDT entry is usable. + /// If the UEFI guest TR is unusable, a new one must be created since we + /// need a host TSS to launch a VM. pub fn is_usable(&self) -> bool { self.access_rights != VMX_INFO_SEGMENT_UNUSABLE } } +/// Given a global descriptor table, and a selector which indexes into the +/// table, unpack the corresponding GDT entry into an UnpackedGdtEntry. pub fn unpack_gdt_entry(gdt: &[GdtEntry], selector: u16) -> UnpackedGdtEntry { let mut unpacked: UnpackedGdtEntry = Default::default(); @@ -51,31 +64,65 @@ pub fn unpack_gdt_entry(gdt: &[GdtEntry], selector: u16) -> UnpackedGdtEntry { unpacked } +/// 32 bit GDT entry. +/// The layout of this structure is determined by hardware. +/// For more information see the Intel manual, Volume 3, Chapter 5 +/// ("Protection"), Section 5.2 "Fields and Flags Used for Segment-Level and +/// Page-Level Protection". +/// See also the OS Dev wiki page on the [GDT](https://wiki.osdev.org/GDT) and +/// the accompanying [tutorial](https://wiki.osdev.org/GDT_Tutorial). #[derive(Debug, Clone, Copy)] #[allow(unused)] #[repr(packed)] pub struct GdtEntry { + /// Low 16 bits of the segment limit. pub limit_low: u16, + /// Low 16 bits of the segment base. pub base_low: u16, + /// Middle 8 bits of the segment base. pub base_middle: u8, + /// Various flags used to set segment type and access rights. pub access: u8, + /// The low 4 bits are part of the limit. The high 4 bits are the + /// granularity of the segment and the size. pub granularity: u8, + /// High 8 bits of the segment base. pub base_high: u8, } +/// 64 bit GDT entry. +/// The layout of this structure is determined by hardware. +/// For more information see the Intel manual, Volume 3, Chapter 3 +/// ("Protected-Mode Memory Management"), "Section 3.5.2 Segment Descriptor +/// Tables in IA-32e Mode". +/// See also Volume 3, Chapter 7 ("Task Mangement"), Section 7.2.3 "TSS +/// Descriptor in 64-bit mode". +/// See also the OS Dev wiki page on the [GDT](https://wiki.osdev.org/GDT) and +/// the accompanying [tutorial](https://wiki.osdev.org/GDT_Tutorial). #[allow(unused)] #[repr(packed)] pub struct GdtEntry64 { + /// Low 16 bits of the segment limit. pub limit_low: u16, + /// Low 16 bits of the segment base. pub base_low: u16, + /// Middle 8 bits of the segment base. pub base_middle: u8, + /// Various flags used to set segment type and access rights. pub access: u8, + /// The low 4 bits are part of the limit. The high 4 bits are the + /// granularity of the segment and the size. pub granularity: u8, + /// Higher 8 bits of the segment base. pub base_high: u8, + /// Highest 32 bits of the segment base. pub base_highest: u32, + /// Reserved 0. pub reserved0: u32, } +/// Get a reference to the processor's current GDT. +/// Note that we can't pub fn get_current_gdt() -> &'static [GdtEntry] { let mut gdtr: x86::dtables::DescriptorTablePointer = Default::default(); unsafe { @@ -91,13 +138,30 @@ pub fn get_current_gdt() -> &'static [GdtEntry] { } } +/// Global Descriptor Table Register. +/// Describes the size and base of the GDT. +/// For more information see the Intel manual, Volume 3, Chapter 2 ("System +/// Architecture Overview"), Section 2.4.1 "Global Descriptor Table Register +/// GDTR)", and the accompanying figure. +/// See also the OS Dev wiki page on the [GDT](https://wiki.osdev.org/GDT) and +/// the accompanying [tutorial](https://wiki.osdev.org/GDT_Tutorial). #[derive(Default)] #[repr(packed)] pub struct GdtDescriptor { + /// The limit of the Global Descriptor Table. + /// That is the size of the GDT minus one. pub limit: u16, + /// The base virtual address of the GDT. pub base: u64, } +/// The Task Struct Segment. +/// This is used for hardware task switching on 32 bit x86 and for holding +/// interrupt stack bases on 64 bit x86. +/// For more information see the Intel manual, Volume 3, Chapter 7 ("Task +/// Management"), Figure 7-11 "64-Bit TSS Format". +/// and the OS Dev wiki page on the +/// [TSS](https://wiki.osdev.org/Task_State_Segment). #[allow(unused)] #[repr(packed)] pub struct Tss { diff --git a/hypervisor/src/vmexit_handlers.rs b/hypervisor/src/vmexit_handlers.rs index 33585f6..ec35b8c 100644 --- a/hypervisor/src/vmexit_handlers.rs +++ b/hypervisor/src/vmexit_handlers.rs @@ -1,9 +1,11 @@ //! This module defines the host's VM exit handlers. //use crate::interrupt_controller; +use crate::hypercall_handler; use crate::register_state::GeneralPurposeRegisterState; use crate::vmcs_dump; use crate::vmcs_fields::VmcsField; use crate::vmexit_reasons::*; +use crate::vmx; use crate::vmx::vmread; use crate::vmx::vmwrite; use log::trace; @@ -20,6 +22,30 @@ fn advance_guest_rip() -> Result<(), x86::vmx::VmFail> { vmwrite(VmcsField::GuestRip, rip) } +/// Handle CPUID +/// In most cases, report back the host's CPUID values with the following exceptions: +/// - Clear the VMX available bit +/// - If RAX has the magic value 'rsty' or 0x72737479 this is a hypercall, so +/// call the hypercall handler. +/// - Do not set the hypervisor bit, to be stealthy. +fn handle_cpuid(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> { + advance_guest_rip()?; + if gprs.rax as u32 == hypervisor_abi::HYPERCALL_MAGIC { + return hypercall_handler::handle_hypercall(gprs); + } + + let mut result = unsafe { core::arch::x86_64::__cpuid(gprs.rax as u32) }; + if gprs.rax == vmx::CPUIDLeaf::ProcessorInfoAndFeatures as u64 { + result.ecx &= vmx::CPUIDLeafProcessorInfoAndFeaturesECXBits::VMXAvailable as u32; + } + gprs.rax = u64::from(result.eax); + gprs.rbx = u64::from(result.ebx); + gprs.rcx = u64::from(result.ecx); + gprs.rdx = u64::from(result.edx); + + Ok(()) +} + /// Emulate control register access. When a control register access VM exit /// occurs, perform that access, e.g. load from the control register or a store /// to it, on the underlying hardware (since this is a mostly passthrough @@ -87,6 +113,7 @@ pub extern "C" fn hypervisor_handle_vmexit(gprs: *mut GeneralPurposeRegisterStat let vmexit_reasion = vmread(VmcsField::VmExitReason).expect("vm exit reason shouldn't error"); let qualification = vmread(VmcsField::ExitQualificatIon).unwrap_or(0); match vmexit_reasion { + VMEXIT_REASON_CPUID => handle_cpuid(gprs).unwrap(), VMEXIT_REASON_CONTROL_REGISTER_ACCESS => { handle_control_register_access(gprs).unwrap(); } diff --git a/hypervisor_abi/Cargo.toml b/hypervisor_abi/Cargo.toml new file mode 100644 index 0000000..d9a4a9c --- /dev/null +++ b/hypervisor_abi/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hypervisor_abi" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/hypervisor_abi/src/lib.rs b/hypervisor_abi/src/lib.rs new file mode 100644 index 0000000..77e06ce --- /dev/null +++ b/hypervisor_abi/src/lib.rs @@ -0,0 +1,35 @@ +//! Hypercall ABI +//! The hypervisor will handle hypercalls via the CPUID instruction. +//! Hypercalls must have a magic number HYPERCALL_MAGIC in RAX and a valid +//! hypercall reason in RBX. +//! Values will be returned in RAX, RBX, RCX, and RDX according to the +//! hypercall reason. + +#![no_std] + +use core::arch::x86_64::__cpuid_count; + +/// Magic number which must be in RAX if this is a hypercall. +pub const HYPERCALL_MAGIC: u32 = 0x72737479; + +/// Hypercall reasons must be in RBX. If RBX=1, the reason is version. +/// The major, minor, and patch version numbers from this crate's Cargo.toml +/// will be returned in rax, rbx, and rcx respectively. Rdx is reserved zero. +pub const HYPERCALL_REASON_VERSION: u32 = 0x1; + +#[derive(Debug, Default, Clone, Copy)] +pub struct HyperCallResults { + // The hypercall "reason" or discriminant. Similar to a syscall number. + pub reason: u32, + // Return values of the hypercall. + pub results: [u32; 4], +} + +pub fn invoke_hypercall(reason: u32) -> HyperCallResults { + let results = unsafe { __cpuid_count(HYPERCALL_MAGIC, reason) }; + + HyperCallResults { + reason, + results: [results.eax, results.ebx, results.ecx, results.edx], + } +} diff --git a/linux/Makefile b/linux/Makefile index 80378d5..ff96482 100644 --- a/linux/Makefile +++ b/linux/Makefile @@ -1,4 +1,4 @@ -KDIR := /lib/modules/$(shell uname -r)/build +KDIR ?= /lib/modules/$(shell uname -r)/build obj-m := rustyvisor.o rustyvisor-y := src/linux_module.o src/isr.o src/host_entrypoint.o liblinux.o diff --git a/linux/src/lib.rs b/linux/src/lib.rs index debb601..079b19b 100644 --- a/linux/src/lib.rs +++ b/linux/src/lib.rs @@ -2,7 +2,10 @@ //! [hypervisor](../hypervisor/index.html) module. //! This code gets compiled into a static library which is linked into the //! kernel module + #![no_std] +#![warn(missing_docs)] + extern crate hypervisor; use core::convert::TryFrom; diff --git a/pcuart/src/lib.rs b/pcuart/src/lib.rs index 5ad8003..4d529d2 100644 --- a/pcuart/src/lib.rs +++ b/pcuart/src/lib.rs @@ -1,15 +1,27 @@ #![no_std] #![feature(asm)] +#![warn(missing_docs)] + +//! A logging crate for writing logs to a PC's COM port. +//! For more information see the Wikipedia page for the +//! [16550 UART //chip](https://en.wikipedia.org/wiki/16550_UART), and the OS +//! Dev wiki page on [Serial Ports](https://wiki.osdev.org/Serial_Ports). use core::fmt; +/// Implements a logging facade over the underlying UART. pub mod logger; +/// The port to be used by the UART object. #[derive(Copy, Clone)] #[repr(u16)] pub enum UartComPort { + /// COM1 port, with IO port 0x3f8 Com1 = 0x3f8, + /// COM2 port, with IO port 0x2f8 Com2 = 0x2f8, + /// COM3 port, with IO port 0x3e8 Com3 = 0x3e8, + /// COM4 port, with IO port 0x4e8 Com4 = 0x2e8, } @@ -26,23 +38,30 @@ const UART_OFFSET_LINE_STATUS: u16 = 5; //const UART_OFFSET_MODEM_STATUS: u16 = 6; //const UART_OFFSET_SCRATCH: u16 = 7; +/// A UART object. #[derive(Default)] pub struct Uart { io_port_base: u16, } +/// The baud rate for the UART. #[derive(Copy, Clone)] pub enum UartBaudRate { + /// Configure the UART to use an 115200 baud rate. Baud115200 = 115200, + /// Configure the UART to use a 9600 baud rate. Baud9600 = 9600, } impl Uart { + /// Creates a new UART on the given COM port. pub const fn new(com: UartComPort) -> Self { Self { io_port_base: com as u16, } } + + /// Configures the UART with the given baud rate. pub fn init(&self, enable_receiver_interrupts: bool, baud_rate: UartBaudRate) { outw(self.io_port_base + UART_OFFSET_INTERRUPT_ENABLE, 0x00); outw(self.io_port_base + UART_OFFSET_LINE_CONTROL, 0x80); diff --git a/pcuart/src/logger.rs b/pcuart/src/logger.rs index e5758d2..22c4fb7 100644 --- a/pcuart/src/logger.rs +++ b/pcuart/src/logger.rs @@ -4,6 +4,8 @@ use super::{Uart, UartBaudRate, UartComPort}; use core::fmt::Write; use spin::Mutex; +/// A logger object suitable to writing logging information to a UART. +/// Unlike the UART object, this handles synchronization. pub struct UartLogger { port: Mutex, } @@ -36,11 +38,13 @@ impl UnsynchronizedUartLogger { } impl UartLogger { + /// Creates a new UART logger which will use the given port. pub const fn new(port: UartComPort) -> Self { Self { port: Mutex::new(Uart::new(port)), } } + /// Configures the UART to use an 115200 baud rate. pub fn init(&self) { self.port.lock().init(false, UartBaudRate::Baud115200); } diff --git a/rustyvctl/.cargo/config b/rustyvctl/.cargo/config new file mode 100644 index 0000000..be5680d --- /dev/null +++ b/rustyvctl/.cargo/config @@ -0,0 +1,3 @@ +[target.x86_64-unknown-uefi] +rustflags = ["-Clink-args= /subsystem:EFI_APPLICATION"] + diff --git a/rustyvctl/Cargo.toml b/rustyvctl/Cargo.toml new file mode 100644 index 0000000..f4bb08f --- /dev/null +++ b/rustyvctl/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rustyvctl" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hypervisor_abi = { path = "../hypervisor_abi" } +uefi = "0.11.0" +x86 = { git = "https://github.com/iankronquist/rust-x86" } +log = { default-features = false, version = "0.4" } \ No newline at end of file diff --git a/rustyvctl/old.build.rs b/rustyvctl/old.build.rs new file mode 100644 index 0000000..66742df --- /dev/null +++ b/rustyvctl/old.build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let os = env::var("CARGO_CFG_TARGET_OS"); + if os == Ok("uefi".to_string()) { + println!("cargo:rustc-flags='-Clink-args= /subsystem:EFI_APPLICATION"); + } +} \ No newline at end of file diff --git a/rustyvctl/src/main.rs b/rustyvctl/src/main.rs new file mode 100644 index 0000000..984e2da --- /dev/null +++ b/rustyvctl/src/main.rs @@ -0,0 +1,65 @@ +//! A UEFI executable for communicating with the +//! [hypervisor](../hypervisor/index.html). +//! Invoke via the UEFI shell like so: +//! ```text +//! UEFI Interactive Shell v2.2 +//! EDK II +//! UEFI v2.70 (EDK II, 0x00010000) +//! Mapping table +//! FS0: Alias(s):F0a:;BLK0: +//! PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0) +//! Press ESC in 1 seconds to skip startup.nsh or any other key to continue. +//! Shell> fs0: +//! FS0:\> dir +//! Directory of: FS0:\ +//! 06/03/2021 23:33 342,016 rustyvctl.efi +//! 06/03/2021 23:42 10,383 NvVars +//! 2 File(s) 433,807 bytes +//! 0 Dir(s) +//! FS0:\> .\rustyvctl.efi +//! FS0:\> +//! ``` + +#![no_std] +#![no_main] +#![feature(abi_efiapi)] +#![warn(missing_docs)] +// Used for ud2 intrinsic. +#![feature(stdsimd)] + +extern crate hypervisor_abi; +extern crate uefi; +//extern crate uefi_services; +extern crate log; + +use core::fmt::Write; + +use uefi::prelude::*; + +/// The entrypoint of the UEFI application. +#[no_mangle] +pub extern "efiapi" fn efi_main( + _image_handle: uefi::Handle, + system_table: SystemTable, +) -> Status { + let results = hypervisor_abi::invoke_hypercall(hypervisor_abi::HYPERCALL_REASON_VERSION); + + let io_result = write!( + system_table.stdout(), + "Hypervisor version {}.{}.{}\r\n", + results.results[0], + results.results[1], + results.results[2] + ); + + match io_result { + Ok(()) => Status::SUCCESS, + Err(_) => Status::WARN_WRITE_FAILURE, + } +} + +/// Handle Rust panics. +#[panic_handler] +pub extern "C" fn panic_fmt(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::arch::x86_64::ud2() } +} diff --git a/scripts/bochs_deploy.sh b/scripts/bochs_deploy.sh index d04b188..d58efed 100644 --- a/scripts/bochs_deploy.sh +++ b/scripts/bochs_deploy.sh @@ -1,2 +1,3 @@ #!/bin/sh -mcopy -i fat.img target/x86_64-unknown-uefi/debug/uefi.efi :: +mcopy -i fat.img target/x86_64-unknown-uefi/debug/rustyvisor.efi :: +mcopy -i fat.img target/x86_64-unknown-uefi/debug/rustyvctl.efi :: diff --git a/scripts/ci.sh b/scripts/ci.sh index e8d49e6..8d51525 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -3,7 +3,10 @@ set -x set -e -cargo +nightly build --target x86_64-unknown-uefi +# Deny warnings on all rust code. +export RUSTFLAGS="-D warnings" + +./build.sh #cargo clippy -- -W clippy::all --target x86_64-unknown-uefi cargo fmt --all -- --check diff --git a/scripts/deploy_wsl.ps1 b/scripts/deploy_wsl.ps1 index 9dd93d6..dc827de 100755 --- a/scripts/deploy_wsl.ps1 +++ b/scripts/deploy_wsl.ps1 @@ -6,7 +6,8 @@ if (-not (Test-Path d:\)) { echo "d:\ not mounted?" exit 1 } -cp .\target\x86_64-unknown-uefi\debug\uefi.efi d:\ +cp .\target\x86_64-unknown-uefi\debug\rustyvisor.efi d:\ +cp .\target\x86_64-unknown-uefi\debug\rustyvctl.efi d:\ # https://serverfault.com/questions/130887/dismount-usb-external-drive-using-powershell $driveEject = New-Object -comObject Shell.Application; $driveEject.Namespace(17).ParseName("D:").InvokeVerb("Eject"); diff --git a/uefi/.cargo/config b/uefi/.cargo/config new file mode 100644 index 0000000..3de28b7 --- /dev/null +++ b/uefi/.cargo/config @@ -0,0 +1,2 @@ +[target.x86_64-unknown-uefi] +rustflags = ["-Clink-args= /subsystem:EFI_RUNTIME_DRIVER"] diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 4231967..6450b8e 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -6,6 +6,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "rustyvisor" +path = "src/main.rs" + [dependencies] hypervisor = { path = "../hypervisor" } uefi = "0.11.0" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index ec8439c..fc70813 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -23,6 +23,7 @@ #![no_std] #![no_main] #![feature(abi_efiapi)] +#![warn(missing_docs)] extern crate hypervisor; extern crate uefi;