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)