From 0e58c7d70eaa82718baf26cef9faa42f61f17fb3 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 18 Sep 2024 18:48:42 +0200 Subject: [PATCH] First version of hypercall interface V2 --- Cargo.lock | 3 +- Cargo.toml | 2 +- uhyve-interface/Cargo.toml | 3 +- uhyve-interface/src/lib.rs | 2 + uhyve-interface/src/v2/mod.rs | 92 +++++++++++++ uhyve-interface/src/v2/parameters.rs | 196 +++++++++++++++++++++++++++ 6 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 uhyve-interface/src/v2/mod.rs create mode 100644 uhyve-interface/src/v2/parameters.rs diff --git a/Cargo.lock b/Cargo.lock index a4af7e61..826a4e41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1495,9 +1495,10 @@ dependencies = [ [[package]] name = "uhyve-interface" -version = "0.1.2" +version = "0.2.0" dependencies = [ "aarch64", + "bitflags 2.6.0", "log", "num_enum", "x86_64", diff --git a/Cargo.toml b/Cargo.toml index 9c365c87..7b9c3365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ mac_address = "1.1" thiserror = "2.0" time = "0.3" tun-tap = { version = "0.1.3", default-features = false } -uhyve-interface = { version = "0.1.1", path = "uhyve-interface", features = ["std"] } +uhyve-interface = { version = "0.2.0", path = "uhyve-interface", features = ["std"] } virtio-bindings = { version = "0.2", features = ["virtio-v4_14_0"] } rftrace = { version = "0.1", optional = true } rftrace-frontend = { version = "0.1", optional = true } diff --git a/uhyve-interface/Cargo.toml b/uhyve-interface/Cargo.toml index 03c2cdc7..06a49623 100644 --- a/uhyve-interface/Cargo.toml +++ b/uhyve-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uhyve-interface" -version = "0.1.2" +version = "0.2.0" edition = "2021" authors = [ "Jonathan Klimt ", @@ -15,6 +15,7 @@ categories = ["os"] [dependencies] num_enum = { version = "0.7", default-features = false } log = { version = "0.4", optional = true } +bitflags = "2.6.0" [features] std = ["dep:log"] diff --git a/uhyve-interface/src/lib.rs b/uhyve-interface/src/lib.rs index 670fa906..0100f78b 100644 --- a/uhyve-interface/src/lib.rs +++ b/uhyve-interface/src/lib.rs @@ -5,6 +5,8 @@ pub mod elf; /// Version 1 of the Hypercall Interface pub mod v1; +/// Version 2 of the Hypercall Interface +pub mod v2; #[cfg(target_arch = "aarch64")] pub use ::aarch64::paging::PhysAddr as GuestPhysAddr; diff --git a/uhyve-interface/src/v2/mod.rs b/uhyve-interface/src/v2/mod.rs new file mode 100644 index 00000000..0438b8ca --- /dev/null +++ b/uhyve-interface/src/v2/mod.rs @@ -0,0 +1,92 @@ +//! # Uhyve Hypervisor Interface V2 +//! +//! The Uhyve hypercall interface works as follows: +//! +//! - The guest writes (or reads) to the respective [`HypercallAddress`](v2::HypercallAddress). The 64-bit value written to that location is the guest's physical memory address of the hypercall's parameter. +//! - The hypervisor handles the hypercall. Depending on the Hypercall, the hypervisor might change the parameters struct in the guest's memory. + +// TODO: Throw this out, once https://github.com/rust-lang/rfcs/issues/2783 or https://github.com/rust-lang/rust/issues/86772 is resolved +use num_enum::TryFromPrimitive; + +pub mod parameters; +use crate::v2::parameters::*; + +/// Enum containing all valid MMIO addresses for hypercalls. +/// +/// The discriminants of this enum are the respective addresses, so one can get the code by calling +/// e.g., `HypercallAddress::Exit as u64`. +#[non_exhaustive] +#[repr(u64)] +#[derive(Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum HypercallAddress { + Exit = 0x1010, + SerialWriteByte = 0x1020, + SerialWriteBuffer = 0x1030, + SerialReadByte = 0x1040, + SerialReadBuffer = 0x1050, + GetTime = 0x1060, + Sleep = 0x1070, + FileWrite = 0x1100, + FileOpen = 0x1110, + FileClose = 0x1120, + FileRead = 0x1130, + FileLseek = 0x1140, + FileUnlink = 0x1150, + SharedMemOpen = 0x1200, + SharedMemClose = 0x1210, +} +impl From> for HypercallAddress { + fn from(value: Hypercall) -> Self { + match value { + Hypercall::Exit(_) => Self::Exit, + Hypercall::FileClose(_) => Self::FileClose, + Hypercall::FileLseek(_) => Self::FileLseek, + Hypercall::FileOpen(_) => Self::FileOpen, + Hypercall::FileRead(_) => Self::FileRead, + Hypercall::FileUnlink(_) => Self::FileUnlink, + Hypercall::FileWrite(_) => Self::FileWrite, + Hypercall::GetTime(_) => Self::GetTime, + Hypercall::SerialReadBuffer(_) => Self::SerialReadBuffer, + Hypercall::SerialReadByte => Self::SerialReadByte, + Hypercall::SerialWriteBuffer(_) => Self::SerialWriteBuffer, + Hypercall::SerialWriteByte(_) => Self::SerialWriteByte, + Hypercall::Sleep(_) => Self::Sleep, + Hypercall::SharedMemOpen(_) => Self::SharedMemOpen, + Hypercall::SharedMemClose(_) => Self::SharedMemClose, + } + } +} + +/// Hypervisor calls available in Uhyve with their respective parameters. See the [module level documentation](crate) on how to invoke them. +#[non_exhaustive] +#[derive(Debug)] +pub enum Hypercall<'a> { + /// Exit the VM and return a status code. + Exit(i32), + FileClose(&'a mut CloseParams), + FileLseek(&'a mut LseekParams), + FileOpen(&'a mut OpenParams), + FileRead(&'a mut ReadPrams), + FileWrite(&'a WriteParams), + FileUnlink(&'a mut UnlinkParams), + /// Write a char to the terminal. + SerialWriteByte(u8), + /// Write a buffer to the terminal + SerialWriteBuffer(&'a SerialWriteBufferParams), + /// Read a single byte from the terminal + SerialReadByte, + /// Read a buffer from the terminal + SerialReadBuffer(&'a SerialReadBufferParams), + SharedMemOpen(&'a SharedMemOpenParams), + SharedMemClose(&'a SharedMemCloseParams), + /// Get system time + GetTime(&'a TimeParams), + /// Suspend the vm for (at least) the specified duration. + Sleep(&'a SleepParams), +} +impl<'a> Hypercall<'a> { + /// Get a hypercall's port address. + pub fn port(self) -> u16 { + HypercallAddress::from(self) as u16 + } +} diff --git a/uhyve-interface/src/v2/parameters.rs b/uhyve-interface/src/v2/parameters.rs new file mode 100644 index 00000000..f736b107 --- /dev/null +++ b/uhyve-interface/src/v2/parameters.rs @@ -0,0 +1,196 @@ +//! Parameters for [Hypercalls](crate::v2::Hypercall). + +use core::num::NonZeroU16; + +use bitflags::bitflags; + +use crate::{GuestPhysAddr, GuestVirtAddr}; + +/// Parameters for a [`Exit`](crate::v2::Hypercall::Exit) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct ExitParams { + /// The return code of the guest. + pub arg: i32, +} + +/// Parameters for a [`FileUnlink`](crate::v2::Hypercall::FileUnlink) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct UnlinkParams { + /// Address of the file that should be unlinked. + pub name: GuestPhysAddr, + /// On success, `0` is returned. On error, `-1` is returned. + pub ret: i32, +} + +/// Parameters for a [`FileWrite`](crate::v2::Hypercall::FileWrite) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct WriteParams { + /// File descriptor of the file. + pub fd: i32, + /// Buffer to be written into the file. + pub buf: GuestVirtAddr, + /// Number of bytes in the buffer to be written. + pub len: usize, +} + +/// Parameters for a [`FileRead`](crate::v2::Hypercall::FileRead) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct ReadPrams { + /// File descriptor of the file. + pub fd: i32, + /// Buffer to read the file into. + pub buf: GuestVirtAddr, + /// Number of bytes to read into the buffer. + pub len: usize, + /// Number of bytes read on success. `-1` on failure. + pub ret: isize, +} + +/// Parameters for a [`FileClose`](crate::v2::Hypercall::FileClose) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct CloseParams { + /// File descriptor of the file. + pub fd: i32, + /// Zero on success, `-1` on failure. + pub ret: i32, +} + +/// Parameters for a [`FileOpen`](crate::v2::Hypercall::FileOpen) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct OpenParams { + /// Pathname of the file to be opened. + pub name: GuestPhysAddr, + /// Posix file access mode flags. + pub flags: i32, + /// Access permissions upon opening/creating a file. + pub mode: i32, + /// File descriptor upon successful opening or `-1` upon failure. + pub ret: i32, +} + +/// Parameters for a [`FileLseek`](crate::v2::Hypercall::FileLseek) hypercall +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct LseekParams { + /// File descriptor of the file. + pub fd: i32, + /// Offset in the file. + pub offset: isize, + /// `whence` value of the lseek call. + pub whence: i32, +} + +/// Parameters for a [`SerialWriteBuffer`](crate::v2::Hypercall::SerialWriteBuffer) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct SerialWriteBufferParams { + /// Address of the buffer to be printed. + pub buf: GuestPhysAddr, + /// Length of the buffer. + pub len: usize, +} + +/// Parameters for a [`SerialReadBuffer`](crate::v2::Hypercall::SerialReadBuffer) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct SerialReadBufferParams { + /// Address to write to. + pub buf: GuestPhysAddr, + /// length of `buf`. + pub maxlen: usize, + /// Amount of bytes acutally written. + pub len: usize, +} + +/// Parameters for a [`GetTime`](crate::v2::Hypercall::GetTime) hypercall. This follows the semantics of POSIX's `struct timeval` +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct TimeParams { + /// Seconds since the Unix Epoch. + pub seconds: u64, + /// Microseconds since the Unix Epoch (in addition to the `seconds`). + pub u_seconds: u64, +} + +/// Parameters for a [`Sleep`](crate::v2::Hypercall::Sleep) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct SleepParams { + /// Desired seconds duration (seconds, milliseconds). + pub sleep_duration: (u16, u16), + /// Actual sleep duration. (Appoximately: does not include vm entry/exit duration). Might not be supported. + pub actual_sleep_duration: Option<(NonZeroU16, NonZeroU16)>, +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum SharedMemOpenError { + /// The shared memory due to invalid parameters. + InvalidParams, + /// New shared memory creation was requested, but the shared memory already exists. + AlreadyExisting, + /// There limit of shared memories is exceeded. + TooManySharedMems, + /// Unspecified error + Unspecified, +} + +#[derive(Debug, Copy, Clone)] +pub struct SharedMemFlags(u8); +bitflags! { + impl SharedMemFlags: u8 { + /// The shared memory should be created if not present. + const CREATE = 0b0000_0001; + /// Return an error if the shared memory exists. + const CREATE_EXCLUSIVE = 0b0000_0010; + /// Map the shared memory in read-only mode. + const READ_ONLY = 0b0000_0100; + /// Experimental = Create a shared memory, that can only be written by the current VM. + const CREATE_EXCLUSIVE_WRITE = 0b0000_1000; + } +} + +/// Parameters for a [`SharedMemOpen`](crate::v2::Hypercall::SharedMemOpen) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct SharedMemOpenParams { + /// Address of the mapped shared memory in the guest. Is set by the host. + pub buf: Result, + /// length of `buf`. + pub len: usize, + /// Address of the shared memory identifier utf8 string. + pub identifier: GuestPhysAddr, + /// length of `identifier` in bytes. + pub identifier_len: usize, + /// Flags for opening the shared memory. + pub flags: SharedMemFlags, +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum SharedMemCloseError { + /// The identifier is not valid. + InvalidIdentifier, + /// The shared memory does not exist. + NotExisting, + /// Unspecified error. + Unspecified, +} + +/// Parameters for a [`SharedMemClose`](crate::v2::Hypercall::SharedMemOpen) hypercall. +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct SharedMemCloseParams { + /// Address of the shared memory identifier utf8 string. + pub identifier: GuestPhysAddr, + /// length of `identifier` in bytes. + pub identifier_len: usize, + /// Flags for Closeing the shared memory. + pub result: Result<(), SharedMemCloseError>, +}