use core::fmt;
use crate::HyperCallCode;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IvcGuestPhysAddr(usize);
impl IvcGuestPhysAddr {
pub const fn new(addr: usize) -> Self {
Self(addr)
}
pub const fn as_usize(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IvcHyperCallError {
UnsupportedArchitecture,
Failed(isize),
}
impl fmt::Display for IvcHyperCallError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedArchitecture => {
write!(f, "IVC hypercall is not supported on this architecture")
}
Self::Failed(status) => write!(f, "IVC hypercall failed with status {status}"),
}
}
}
impl core::error::Error for IvcHyperCallError {}
pub type IvcHyperCallResult<T> = Result<T, IvcHyperCallError>;
pub fn publish_channel(
key: usize,
shm_base_gpa_ptr: IvcGuestPhysAddr,
shm_size_ptr: IvcGuestPhysAddr,
) -> IvcHyperCallResult<()> {
execute_checked(publish_channel_invocation(
key,
shm_base_gpa_ptr,
shm_size_ptr,
))
}
pub fn subscribe_channel(
publisher_vm_id: usize,
key: usize,
shm_base_gpa_ptr: IvcGuestPhysAddr,
shm_size_ptr: IvcGuestPhysAddr,
) -> IvcHyperCallResult<()> {
execute_checked(subscribe_channel_invocation(
publisher_vm_id,
key,
shm_base_gpa_ptr,
shm_size_ptr,
))
}
pub fn unpublish_channel(key: usize) -> IvcHyperCallResult<()> {
execute_checked(HyperCallInvocation::new(
HyperCallCode::HIVCUnPublishChannel,
[key, 0, 0, 0, 0, 0],
))
}
pub fn unsubscribe_channel(publisher_vm_id: usize, key: usize) -> IvcHyperCallResult<()> {
execute_checked(HyperCallInvocation::new(
HyperCallCode::HIVCUnSubscribChannel,
[publisher_vm_id, key, 0, 0, 0, 0],
))
}
pub fn notify_channel(
publisher_vm_id: usize,
key: usize,
target_vm_id: usize,
) -> IvcHyperCallResult<()> {
execute_checked(notify_channel_invocation(
publisher_vm_id,
key,
target_vm_id,
))
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct HyperCallInvocation {
code: HyperCallCode,
args: [usize; 6],
}
impl HyperCallInvocation {
const fn new(code: HyperCallCode, args: [usize; 6]) -> Self {
Self { code, args }
}
}
fn publish_channel_invocation(
key: usize,
shm_base_gpa_ptr: IvcGuestPhysAddr,
shm_size_ptr: IvcGuestPhysAddr,
) -> HyperCallInvocation {
HyperCallInvocation::new(
HyperCallCode::HIVCPublishChannel,
[
key,
shm_base_gpa_ptr.as_usize(),
shm_size_ptr.as_usize(),
0,
0,
0,
],
)
}
fn subscribe_channel_invocation(
publisher_vm_id: usize,
key: usize,
shm_base_gpa_ptr: IvcGuestPhysAddr,
shm_size_ptr: IvcGuestPhysAddr,
) -> HyperCallInvocation {
HyperCallInvocation::new(
HyperCallCode::HIVCSubscribChannel,
[
publisher_vm_id,
key,
shm_base_gpa_ptr.as_usize(),
shm_size_ptr.as_usize(),
0,
0,
],
)
}
fn notify_channel_invocation(
publisher_vm_id: usize,
key: usize,
target_vm_id: usize,
) -> HyperCallInvocation {
HyperCallInvocation::new(
HyperCallCode::HIVCNotify,
[publisher_vm_id, key, target_vm_id, 0, 0, 0],
)
}
fn execute_checked(invocation: HyperCallInvocation) -> IvcHyperCallResult<()> {
let status = issue_hypercall(invocation)?;
if status == 0 {
Ok(())
} else {
Err(IvcHyperCallError::Failed(status))
}
}
#[cfg(target_arch = "aarch64")]
fn issue_hypercall(invocation: HyperCallInvocation) -> IvcHyperCallResult<isize> {
let mut x0 = invocation.code as usize;
let args = invocation.args;
unsafe {
core::arch::asm!(
"hvc #0",
"nop",
inlateout("x0") x0,
in("x1") args[0],
in("x2") args[1],
in("x3") args[2],
in("x4") args[3],
in("x5") args[4],
in("x6") args[5],
options(nostack),
);
}
Ok(x0 as isize)
}
#[cfg(target_arch = "x86_64")]
fn issue_hypercall(invocation: HyperCallInvocation) -> IvcHyperCallResult<isize> {
let mut rax = invocation.code as usize;
let args = invocation.args;
unsafe {
core::arch::asm!(
"vmcall",
inlateout("rax") rax,
in("rdi") args[0],
in("rsi") args[1],
in("rdx") args[2],
in("rcx") args[3],
in("r8") args[4],
in("r9") args[5],
options(nostack),
);
}
Ok(rax as isize)
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn issue_hypercall(_invocation: HyperCallInvocation) -> IvcHyperCallResult<isize> {
Err(IvcHyperCallError::UnsupportedArchitecture)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn publish_invocation_matches_axvisor_abi() {
let invocation = publish_channel_invocation(
0x33,
IvcGuestPhysAddr::new(0x8000),
IvcGuestPhysAddr::new(0x9000),
);
assert_eq!(invocation.code, HyperCallCode::HIVCPublishChannel);
assert_eq!(invocation.args, [0x33, 0x8000, 0x9000, 0, 0, 0]);
}
#[test]
fn subscribe_invocation_matches_axvisor_abi() {
let invocation = subscribe_channel_invocation(
1,
0x33,
IvcGuestPhysAddr::new(0x8000),
IvcGuestPhysAddr::new(0x9000),
);
assert_eq!(invocation.code, HyperCallCode::HIVCSubscribChannel);
assert_eq!(invocation.args, [1, 0x33, 0x8000, 0x9000, 0, 0]);
}
#[test]
fn notify_invocation_matches_axvisor_abi() {
let invocation = notify_channel_invocation(1, 0x33, 2);
assert_eq!(invocation.code, HyperCallCode::HIVCNotify);
assert_eq!(invocation.args, [1, 0x33, 2, 0, 0, 0]);
}
}