Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce generic hypercall ioctl #128

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions mshv-bindings/src/hvcall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright © 2024, Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//

use crate::bindings::*;
use std::mem::size_of;
use std::vec::Vec;

/// This file contains helper functions for the MSHV_ROOT_HVCALL ioctl.
/// MSHV_ROOT_HVCALL is basically a 'passthrough' hypercall. The kernel makes a
/// hypercall on behalf of the VMM without interpreting the arguments or result
/// or changing any state in the kernel.

/// RepInput<T> wraps a buffer containing the input for a "rep"[1] hypercall.
/// Rep hypercalls have rep-eated data, i.e. a variable length array as part of
/// the input structure e.g.:
/// ```
/// use mshv_bindings::bindings::*;
/// #[repr(C, packed)]
/// struct hv_input_foo {
/// some_field: __u64,
/// variable_array_field: __IncompleteArrayField<__u64>,
/// }
/// ```
/// The struct cannot be used as-is because it can't store anything in the
/// __IncompleteArrayField<T> field.
///
/// RepInput<T> abstracts a rep hypercall input by wrapping a Vec<T>, where T
/// is the hv_input_* struct type. The buffer backing the Vec<T> has enough
/// space to store both the hv_input_* struct (at index 0), and the rep data
/// immediately following it.
///
/// Note also that the length of the variable length array field is not stored in
/// this struct. Rather, it is passed to the hypercall in the 'rep count' field
/// of the hypercall args (mshv_root_hvcall.reps). RepInput<T> stores this count,
/// along with the size of the entire input data.
///
/// RepInput<T> is intended to be created with make_rep_input!() and used with
/// make_rep_args!() below.
///
/// [1] HyperV TLFS describing the hypercall interface and rep hypercalls:
/// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercall-interface
///
pub struct RepInput<T> {
vec: Vec<T>,
size: usize,
rep_count: usize,
}
impl<T> RepInput<T> {
/// Create a RepInput<T> for a rep hypercall
///
/// # Arguments
///
/// * `vec` - Vec<T> Created via vec_with_array_field(). T is hv_input_* struct
/// * `size` - Size of the hypercall input, including the rep data
/// * `rep_count` - number of reps
pub fn new(vec: Vec<T>, size: usize, rep_count: usize) -> Self {
russell-islam marked this conversation as resolved.
Show resolved Hide resolved
Self {
vec,
size,
rep_count,
}
}
pub fn as_mut_struct_ref(&mut self) -> &mut T {
&mut self.vec[0]
}
pub fn as_struct_ptr(&self) -> *const T {
NunoDasNeves marked this conversation as resolved.
Show resolved Hide resolved
&self.vec[0]
}
pub fn rep_count(&self) -> usize {
self.rep_count
}
pub fn size(&self) -> usize {
self.size
}
}

/// Make `Vec<T>` with at least enough space for `count` + 1 entries of
/// `entry_size`, where `entry_size` != size_of::<T>()
/// The first element of the Vec will hold the T data, and the rest will hold
/// elements of size `entry_size` (note the Vec cannot be used normally to get
/// at these)
pub fn vec_with_array_field<T: Default>(t: T, entry_size: usize, count: usize) -> Vec<T> {
let element_space = count * entry_size;
let vec_size_bytes = size_of::<T>() + element_space;
let rounded_size = (vec_size_bytes + size_of::<T>() - 1) / size_of::<T>();
let mut v = Vec::with_capacity(rounded_size);
v.resize_with(rounded_size, T::default);
v[0] = t;
v
}

/// Utility for casting __IncompleteArrayField<T> as the interior type
pub fn arr_field_as_entry_ptr_mut<T>(ptr: *mut __IncompleteArrayField<T>) -> *mut T {
ptr as *mut T
}

/// Utility for getting the interior type of a __IncompleteArrayField<T>
/// Note we must use a raw pointer rather than a reference here, because the
/// field itself may be unaligned due to the struct being packed
pub fn arr_field_entry_size<T>(_: *const __IncompleteArrayField<T>) -> usize {
size_of::<T>()
}

/// Assemble a RepInput<T> from a hypercall input struct and an array of rep data
/// Arguments:
/// 1. The hv_input_* struct with the input data
/// 2. Name of the __IncompleteArrayField<T> in the struct
/// 3. An array or slice containing the rep data
#[macro_export]
macro_rules! make_rep_input {
($struct_expr:expr, $field_ident:ident, $arr_expr:expr) => {{
let s = $struct_expr;
let a = $arr_expr;
let el_size = arr_field_entry_size(std::ptr::addr_of!(s.$field_ident));
let struct_size = std::mem::size_of_val(&s);
let mut vec = vec_with_array_field(s, el_size, a.len());
// SAFETY: we know the vector is large enough to hold the data
let ptr = arr_field_as_entry_ptr_mut(std::ptr::addr_of_mut!(vec[0].$field_ident));
for (i, el) in a.iter().enumerate() {
unsafe {
let mut p = ptr.add(i);
p.write_unaligned(*el);
};
}
RepInput::new(vec, struct_size + el_size * a.len(), a.len())
}};
}

/// Create a mshv_root_hvcall populated with rep hypercall parameters
/// Arguments:
/// 1. hypercall code
/// 2. RepInput<T> structure, where T is hv_input_*. See make_rep_input!()
/// 3. Slice of the correct type for output data (optional)
#[macro_export]
macro_rules! make_rep_args {
($code_expr:expr, $input_ident:ident, $output_slice_ident:ident) => {{
mshv_root_hvcall {
code: $code_expr as u16,
reps: $input_ident.rep_count() as u16,
in_sz: $input_ident.size() as u16,
out_sz: (std::mem::size_of_val(&$output_slice_ident[0]) * $output_slice_ident.len())
as u16,
in_ptr: $input_ident.as_struct_ptr() as u64,
out_ptr: std::ptr::addr_of_mut!($output_slice_ident[0]) as u64,
..Default::default()
}
}};
($code_expr:expr, $input_ident:ident) => {{
mshv_root_hvcall {
code: $code_expr as u16,
reps: $input_ident.rep_count() as u16,
in_sz: $input_ident.size() as u16,
in_ptr: $input_ident.as_struct_ptr() as u64,
..Default::default()
}
}};
}

/// Create a mshv_root_hvcall populated with hypercall parameters
/// Arguments:
/// 1. hypercall code
/// 2. hv_input_* structure
/// 3. hv_output_* structure (optional)
#[macro_export]
macro_rules! make_args {
($code_expr:expr, $input_ident:ident, $output_ident:ident) => {{
mshv_root_hvcall {
code: $code_expr as u16,
in_sz: std::mem::size_of_val(&$input_ident) as u16,
out_sz: std::mem::size_of_val(&$output_ident) as u16,
in_ptr: std::ptr::addr_of!($input_ident) as u64,
out_ptr: std::ptr::addr_of!($output_ident) as u64,
..Default::default()
}
}};
($code_expr:expr, $input_ident:ident) => {{
mshv_root_hvcall {
code: $code_expr as u16,
in_sz: std::mem::size_of_val(&$input_ident) as u16,
in_ptr: std::ptr::addr_of!($input_ident) as u64,
..Default::default()
}
}};
}
9 changes: 6 additions & 3 deletions mshv-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use self::x86_64::*;

pub mod hvdef;
pub use hvdef::*;

#[cfg(target_arch = "aarch64")]
mod arm64;
#[cfg(target_arch = "aarch64")]
pub use self::arm64::*;

pub mod hvdef;
pub use hvdef::*;

pub mod hvcall;
pub use hvcall::*;
13 changes: 3 additions & 10 deletions mshv-ioctls/src/ioctls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
use mshv_bindings::{mshv_root_hvcall, HvError};
use mshv_bindings::{mshv_root_hvcall, HvError, HV_STATUS_SUCCESS};
use thiserror::Error;
use vmm_sys_util::errno;
pub mod device;
Expand Down Expand Up @@ -47,8 +47,7 @@ impl MshvError {
/// * `ret_args` - MSHV_ROOT_HVCALL args struct, after the ioctl completed
pub fn from_hvcall(error: errno::Error, ret_args: mshv_root_hvcall) -> Self {
use std::convert::TryFrom;
// EIO signals that the hypercall itself may have failed
if error.errno() == libc::EIO && ret_args.status != 0 {
if ret_args.status != HV_STATUS_SUCCESS as u16 {
let hv_err = HvError::try_from(ret_args.status);
return MshvError::Hypercall {
code: ret_args.code,
Expand Down Expand Up @@ -138,15 +137,9 @@ mod tests {
status: HV_STATUS_SUCCESS as u16,
..Default::default()
};
{
let ioctl_err = errno::Error::new(libc::EFAULT);
let mshv_err = MshvError::from_hvcall(ioctl_err, args);

assert!(mshv_err == MshvError::Errno(ioctl_err));
}
{
// special case: EIO with successful hypercall
let ioctl_err = errno::Error::new(libc::EIO);
let ioctl_err = errno::Error::new(libc::EINVAL);
let mshv_err = MshvError::from_hvcall(ioctl_err, args);

assert!(mshv_err == MshvError::Errno(ioctl_err));
Expand Down
Loading