libhypervisor_backends: add basic x86 support
Make the hypervisor_backends library available for x86 with the basic
support:
- Determine the hypervisor type based on cpuid and distinguish if
running Protected or Regular VMs based on it.
- Add support for page sharing which is required for
ProtectedKvmHypervisor case.
Test: Successfully use libhypervisor_backends in Trusty for x86_64.
Run: `m libhypervisor_backends` for x86_64 target.
Bug: 373530687
Bug: 375569676
Change-Id: I675098fa3750084a605d0de50ba43ea26c957768
diff --git a/libs/libhypervisor_backends/Android.bp b/libs/libhypervisor_backends/Android.bp
index 27e3fe5..e9e8915 100644
--- a/libs/libhypervisor_backends/Android.bp
+++ b/libs/libhypervisor_backends/Android.bp
@@ -11,20 +11,27 @@
host_supported: false,
no_stdlibs: true,
srcs: ["src/lib.rs"],
- rustlibs: [
- "libonce_cell_nostd",
- "libsmccc",
- "libthiserror_nostd",
- "libuuid_nostd",
- ],
enabled: false,
+ stdlibs: [
+ "libcompiler_builtins.rust_sysroot",
+ "libcore.rust_sysroot",
+ ],
target: {
android_arm64: {
- enabled: true,
- stdlibs: [
- "libcompiler_builtins.rust_sysroot",
- "libcore.rust_sysroot",
+ rustlibs: [
+ "libonce_cell_nostd",
+ "libsmccc",
+ "libthiserror_nostd",
+ "libuuid_nostd",
],
+ enabled: true,
+ },
+ android_x86_64: {
+ rustlibs: [
+ "libonce_cell_nostd",
+ "libthiserror_nostd",
+ ],
+ enabled: true,
},
},
}
diff --git a/libs/libhypervisor_backends/src/error.rs b/libs/libhypervisor_backends/src/error.rs
index e9c37e1..3046b0c 100644
--- a/libs/libhypervisor_backends/src/error.rs
+++ b/libs/libhypervisor_backends/src/error.rs
@@ -16,7 +16,10 @@
use core::{fmt, result};
-use super::hypervisor::{GeniezoneError, KvmError};
+#[cfg(target_arch = "aarch64")]
+use super::hypervisor::GeniezoneError;
+use super::hypervisor::KvmError;
+#[cfg(target_arch = "aarch64")]
use uuid::Uuid;
/// Result type with hypervisor error.
@@ -29,10 +32,15 @@
MmioGuardNotSupported,
/// Failed to invoke a certain KVM HVC function.
KvmError(KvmError, u32),
+ #[cfg(target_arch = "aarch64")]
/// Failed to invoke GenieZone HVC function.
GeniezoneError(GeniezoneError, u32),
+ #[cfg(target_arch = "aarch64")]
/// Unsupported Hypervisor
UnsupportedHypervisorUuid(Uuid),
+ #[cfg(target_arch = "x86_64")]
+ /// Unsupported x86_64 Hypervisor
+ UnsupportedHypervisor(u128),
}
impl fmt::Display for Error {
@@ -42,15 +50,21 @@
Self::KvmError(e, function_id) => {
write!(f, "Failed to invoke the HVC function with function ID {function_id}: {e}")
}
+ #[cfg(target_arch = "aarch64")]
Self::GeniezoneError(e, function_id) => {
write!(
f,
"Failed to invoke GenieZone HVC function with function ID {function_id}: {e}"
)
}
+ #[cfg(target_arch = "aarch64")]
Self::UnsupportedHypervisorUuid(u) => {
write!(f, "Unsupported Hypervisor UUID {u}")
}
+ #[cfg(target_arch = "x86_64")]
+ Self::UnsupportedHypervisor(c) => {
+ write!(f, "Unsupported x86_64 Hypervisor {c}")
+ }
}
}
}
diff --git a/libs/libhypervisor_backends/src/hypervisor.rs b/libs/libhypervisor_backends/src/hypervisor.rs
index e228e8c..aa65133 100644
--- a/libs/libhypervisor_backends/src/hypervisor.rs
+++ b/libs/libhypervisor_backends/src/hypervisor.rs
@@ -15,28 +15,42 @@
//! Wrappers around hypervisor back-ends.
mod common;
+#[cfg(target_arch = "aarch64")]
mod geniezone;
+#[cfg(target_arch = "aarch64")]
mod gunyah;
#[cfg(target_arch = "aarch64")]
#[path = "hypervisor/kvm_aarch64.rs"]
mod kvm;
-use super::{Error, Result};
+#[cfg(target_arch = "x86_64")]
+#[path = "hypervisor/kvm_x86.rs"]
+mod kvm;
+
+#[cfg(target_arch = "aarch64")]
+use {
+ super::{Error, Result},
+ geniezone::GeniezoneHypervisor,
+ gunyah::GunyahHypervisor,
+ smccc::hvc64,
+ uuid::Uuid,
+};
+
+#[cfg(target_arch = "aarch64")]
+pub use geniezone::GeniezoneError;
+
use alloc::boxed::Box;
use common::Hypervisor;
pub use common::{DeviceAssigningHypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
-pub use geniezone::GeniezoneError;
-use geniezone::GeniezoneHypervisor;
-use gunyah::GunyahHypervisor;
pub use kvm::KvmError;
use kvm::{ProtectedKvmHypervisor, RegularKvmHypervisor};
use once_cell::race::OnceBox;
-use smccc::hvc64;
-use uuid::Uuid;
enum HypervisorBackend {
RegularKvm,
+ #[cfg(target_arch = "aarch64")]
Gunyah,
+ #[cfg(target_arch = "aarch64")]
Geniezone,
ProtectedKvm,
}
@@ -45,13 +59,16 @@
fn get_hypervisor(&self) -> &'static dyn Hypervisor {
match self {
Self::RegularKvm => &RegularKvmHypervisor,
+ #[cfg(target_arch = "aarch64")]
Self::Gunyah => &GunyahHypervisor,
+ #[cfg(target_arch = "aarch64")]
Self::Geniezone => &GeniezoneHypervisor,
Self::ProtectedKvm => &ProtectedKvmHypervisor,
}
}
}
+#[cfg(target_arch = "aarch64")]
impl TryFrom<Uuid> for HypervisorBackend {
type Error = Error;
@@ -76,8 +93,10 @@
}
}
+#[cfg(target_arch = "aarch64")]
const ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID: u32 = 0x8600ff01;
+#[cfg(target_arch = "aarch64")]
fn query_vendor_hyp_call_uid() -> Uuid {
let args = [0u64; 17];
let res = hvc64(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, args);
@@ -103,7 +122,13 @@
}
fn detect_hypervisor() -> HypervisorBackend {
- query_vendor_hyp_call_uid().try_into().expect("Failed to detect hypervisor")
+ #[cfg(target_arch = "aarch64")]
+ {
+ query_vendor_hyp_call_uid().try_into().expect("Failed to detect hypervisor")
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ kvm::determine_hyp_type().expect("Failed to detect hypervisor")
}
/// Gets the hypervisor singleton.
diff --git a/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs b/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs
new file mode 100644
index 0000000..7f9ea4d
--- /dev/null
+++ b/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs
@@ -0,0 +1,172 @@
+// Copyright 2025, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Wrappers around calls to the KVM hypervisor.
+
+use super::{Hypervisor, MemSharingHypervisor};
+use crate::{mem::SIZE_4KB, Error, Result};
+use core::fmt::{self, Display, Formatter};
+
+const KVM_HC_PKVM_OP: u32 = 20;
+const PKVM_GHC_SHARE_MEM: u32 = KVM_HC_PKVM_OP + 1;
+const PKVM_GHC_UNSHARE_MEM: u32 = KVM_HC_PKVM_OP + 2;
+
+const KVM_ENOSYS: i64 = -1000;
+const KVM_EINVAL: i64 = -22;
+
+/// This CPUID returns the signature and can be used to determine if VM is running under pKVM, KVM
+/// or not.
+pub const KVM_CPUID_SIGNATURE: u32 = 0x40000000;
+
+/// Error from a KVM HVC call.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum KvmError {
+ /// The call is not supported by the implementation.
+ NotSupported,
+ /// One of the call parameters has a non-supported value.
+ InvalidParameter,
+ /// There was an unexpected return value.
+ Unknown(i64),
+}
+
+impl From<i64> for KvmError {
+ fn from(value: i64) -> Self {
+ match value {
+ KVM_ENOSYS => KvmError::NotSupported,
+ KVM_EINVAL => KvmError::InvalidParameter,
+ _ => KvmError::Unknown(value),
+ }
+ }
+}
+
+impl From<i32> for KvmError {
+ fn from(value: i32) -> Self {
+ i64::from(value).into()
+ }
+}
+
+impl Display for KvmError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::NotSupported => write!(f, "KVM call not supported"),
+ Self::InvalidParameter => write!(f, "KVM call received non-supported value"),
+ Self::Unknown(e) => write!(f, "Unknown return value from KVM {} ({0:#x})", e),
+ }
+ }
+}
+
+pub(super) struct RegularKvmHypervisor;
+
+impl RegularKvmHypervisor {
+ pub(super) const CPUID: u128 = u128::from_le_bytes(*b"KVMKVMKVM\0\0\0\0\0\0\0");
+}
+
+impl Hypervisor for RegularKvmHypervisor {}
+
+pub(super) struct ProtectedKvmHypervisor;
+
+impl ProtectedKvmHypervisor {
+ pub(super) const CPUID: u128 = u128::from_le_bytes(*b"PKVMPKVMPKVM\0\0\0\0");
+}
+
+impl Hypervisor for ProtectedKvmHypervisor {
+ fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
+ Some(self)
+ }
+}
+
+macro_rules! vmcall {
+ ($hypcall:expr, $base:expr, $size:expr) => {{
+ let ret;
+ // SAFETY:
+ // Any undeclared register aren't clobbered except rbx but rbx value is restored at the end
+ // of the asm block.
+ unsafe {
+ core::arch::asm!(
+ "xchg %rbx, {0:r}",
+ "vmcall",
+ "xchg %rbx, {0:r}",
+ in(reg) $base,
+ inout("rax") $hypcall => ret,
+ in("rcx") $size,
+ options(att_syntax, nomem));
+ };
+ ret
+ }};
+}
+
+macro_rules! cpuid {
+ ($hypcall:expr) => {{
+ let ret_1: u32;
+ let ret_2: u32;
+ let ret_3: u32;
+ // SAFETY:
+ // Any undeclared register aren't clobbered except rbx but rbx value is restored at the end
+ // of the asm block.
+ unsafe {
+ // The argument for cpuid is passed via rax and in case of KVM_CPUID_SIGNATURE returned
+ // via rbx, rcx and rdx. Ideally using named arguments in inline asm for rbx would be
+ // much more straightforward but when rbx is directly used LLVM complains that: error:
+ // cannot use register `bx`: rbx is used internally by LLVM and cannot be used as an
+ // operand for inline asm
+ //
+ // Therefore use temp register to store rbx content and restore it back after cpuid
+ // call.
+ core::arch::asm!(
+ "xchg %rbx, {0:r}",
+ "cpuid",
+ "xchg %rbx, {0:r}",
+ out(reg) ret_1, in("eax") $hypcall, out("rcx") ret_2, out ("rdx") ret_3,
+ options(att_syntax, nomem));
+ };
+ ((ret_3 as u128) << 64) | ((ret_2 as u128) << 32) | (ret_1 as u128)
+ }};
+}
+
+impl MemSharingHypervisor for ProtectedKvmHypervisor {
+ fn share(&self, base_ipa: u64) -> Result<()> {
+ let ret: u32 = vmcall!(PKVM_GHC_SHARE_MEM, base_ipa, SIZE_4KB);
+
+ if ret != 0 {
+ return Err(Error::KvmError(KvmError::from(ret as i32), PKVM_GHC_SHARE_MEM));
+ }
+
+ Ok(())
+ }
+
+ fn unshare(&self, base_ipa: u64) -> Result<()> {
+ let ret: u32 = vmcall!(PKVM_GHC_UNSHARE_MEM, base_ipa, SIZE_4KB);
+ if ret != 0 {
+ return Err(Error::KvmError(KvmError::from(ret as i32), PKVM_GHC_UNSHARE_MEM));
+ }
+
+ Ok(())
+ }
+
+ fn granule(&self) -> Result<usize> {
+ Ok(SIZE_4KB)
+ }
+}
+
+use crate::hypervisor::HypervisorBackend;
+
+pub(crate) fn determine_hyp_type() -> Result<HypervisorBackend> {
+ let cpuid: u128 = cpuid!(KVM_CPUID_SIGNATURE);
+
+ match cpuid {
+ RegularKvmHypervisor::CPUID => Ok(HypervisorBackend::RegularKvm),
+ ProtectedKvmHypervisor::CPUID => Ok(HypervisorBackend::ProtectedKvm),
+ c => Err(Error::UnsupportedHypervisor(c)),
+ }
+}
diff --git a/libs/libhypervisor_backends/src/mem.rs b/libs/libhypervisor_backends/src/mem.rs
index ff65c49..9f7eafc 100644
--- a/libs/libhypervisor_backends/src/mem.rs
+++ b/libs/libhypervisor_backends/src/mem.rs
@@ -15,6 +15,7 @@
/// The size of a 4KB memory in bytes.
pub const SIZE_4KB: usize = 4 << 10;
+#[cfg(target_arch = "aarch64")]
/// Computes the largest multiple of the provided alignment smaller or equal to the address.
///
/// Note: the result is undefined if alignment isn't a power of two.
@@ -22,6 +23,7 @@
addr & !(alignment - 1)
}
+#[cfg(target_arch = "aarch64")]
/// Computes the address of the 4KiB page containing a given address.
pub const fn page_4kb_of(addr: usize) -> usize {
unchecked_align_down(addr, SIZE_4KB)