vmbase: Integrate libhyp as module

The only client of libhyp should ever be libvmbase so integrate the
former into the latter, which will allow code de-dup, easier tracking of
logic (including global static variables) and more straightforward code
structure.

Bug: 279539763
Test: m libpvmfw libvmbase_example librialto
Change-Id: Icbeda4cc936ead9c6c6df0533089ddb6b88a0833
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 07e1b4c..236a895 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -79,13 +79,13 @@
         "libbuddy_system_allocator",
         "libcstr",
         "libfdtpci",
-        "libhyp",
         "liblibfdt",
         "liblog_rust_nostd",
         "libonce_cell_nostd",
         "libsmccc",
         "libspin_nostd",
         "libtinyvec_nostd",
+        "libuuid_nostd",
         "libvirtio_drivers",
         "libzerocopy_nostd",
         "libzeroize_nostd",
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index b19efce..8b1d7c6 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -15,17 +15,16 @@
 //! Rust entry point.
 
 use crate::{
-    bionic, console, heap, logger,
+    bionic, console, heap, hyp, logger,
     power::{reboot, shutdown},
     rand,
 };
 use core::mem::size_of;
-use hyp::{self, get_mmio_guard};
 
 fn try_console_init() -> Result<(), hyp::Error> {
     console::init();
 
-    if let Some(mmio_guard) = get_mmio_guard() {
+    if let Some(mmio_guard) = hyp::get_mmio_guard() {
         mmio_guard.enroll()?;
         mmio_guard.validate_granule()?;
         mmio_guard.map(console::BASE_ADDRESS)?;
diff --git a/vmbase/src/hyp.rs b/vmbase/src/hyp.rs
new file mode 100644
index 0000000..103d28f
--- /dev/null
+++ b/vmbase/src/hyp.rs
@@ -0,0 +1,25 @@
+// Copyright 2023, 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.
+
+//! This library provides wrappers around various hypervisor backends.
+
+mod error;
+mod hypervisor;
+mod util;
+
+pub use error::{Error, Result};
+pub use hypervisor::{
+    get_device_assigner, get_mem_sharer, get_mmio_guard, DeviceAssigningHypervisor, KvmError,
+    MMIO_GUARD_GRANULE_SIZE,
+};
diff --git a/vmbase/src/hyp/error.rs b/vmbase/src/hyp/error.rs
new file mode 100644
index 0000000..bbf706e
--- /dev/null
+++ b/vmbase/src/hyp/error.rs
@@ -0,0 +1,61 @@
+// Copyright 2023, 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.
+
+//! Error and Result types for hypervisor.
+
+use core::{fmt, result};
+
+use super::hypervisor::{GeniezoneError, KvmError};
+use uuid::Uuid;
+
+/// Result type with hypervisor error.
+pub type Result<T> = result::Result<T, Error>;
+
+/// Hypervisor error.
+#[derive(Debug, Clone)]
+pub enum Error {
+    /// MMIO guard is not supported.
+    MmioGuardNotSupported,
+    /// Failed to invoke a certain KVM HVC function.
+    KvmError(KvmError, u32),
+    /// Failed to invoke GenieZone HVC function.
+    GeniezoneError(GeniezoneError, u32),
+    /// Unsupported Hypervisor
+    UnsupportedHypervisorUuid(Uuid),
+    /// The MMIO_GUARD granule used by the hypervisor is not supported.
+    UnsupportedMmioGuardGranule(usize),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::MmioGuardNotSupported => write!(f, "MMIO guard is not supported"),
+            Self::KvmError(e, function_id) => {
+                write!(f, "Failed to invoke the HVC function with function ID {function_id}: {e}")
+            }
+            Self::GeniezoneError(e, function_id) => {
+                write!(
+                    f,
+                    "Failed to invoke GenieZone HVC function with function ID {function_id}: {e}"
+                )
+            }
+            Self::UnsupportedHypervisorUuid(u) => {
+                write!(f, "Unsupported Hypervisor UUID {u}")
+            }
+            Self::UnsupportedMmioGuardGranule(g) => {
+                write!(f, "Unsupported MMIO guard granule: {g}")
+            }
+        }
+    }
+}
diff --git a/vmbase/src/hyp/hypervisor.rs b/vmbase/src/hyp/hypervisor.rs
new file mode 100644
index 0000000..dab15ec
--- /dev/null
+++ b/vmbase/src/hyp/hypervisor.rs
@@ -0,0 +1,129 @@
+// Copyright 2023, 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 hypervisor back-ends.
+
+mod common;
+mod geniezone;
+mod gunyah;
+mod kvm;
+
+use super::{Error, Result};
+use alloc::boxed::Box;
+use common::Hypervisor;
+pub use common::{
+    DeviceAssigningHypervisor, MemSharingHypervisor, MmioGuardedHypervisor, MMIO_GUARD_GRANULE_SIZE,
+};
+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,
+    Gunyah,
+    Geniezone,
+    ProtectedKvm,
+}
+
+impl HypervisorBackend {
+    fn get_hypervisor(&self) -> &'static dyn Hypervisor {
+        match self {
+            Self::RegularKvm => &RegularKvmHypervisor,
+            Self::Gunyah => &GunyahHypervisor,
+            Self::Geniezone => &GeniezoneHypervisor,
+            Self::ProtectedKvm => &ProtectedKvmHypervisor,
+        }
+    }
+}
+
+impl TryFrom<Uuid> for HypervisorBackend {
+    type Error = Error;
+
+    fn try_from(uuid: Uuid) -> Result<HypervisorBackend> {
+        match uuid {
+            GeniezoneHypervisor::UUID => Ok(HypervisorBackend::Geniezone),
+            GunyahHypervisor::UUID => Ok(HypervisorBackend::Gunyah),
+            RegularKvmHypervisor::UUID => {
+                // Protected KVM has the same UUID as "regular" KVM so issue an HVC that is assumed
+                // to only be supported by pKVM: if it returns SUCCESS, deduce that this is pKVM
+                // and if it returns NOT_SUPPORTED assume that it is "regular" KVM.
+                match ProtectedKvmHypervisor.as_mmio_guard().unwrap().granule() {
+                    Ok(_) => Ok(HypervisorBackend::ProtectedKvm),
+                    Err(Error::KvmError(KvmError::NotSupported, _)) => {
+                        Ok(HypervisorBackend::RegularKvm)
+                    }
+                    Err(e) => Err(e),
+                }
+            }
+            u => Err(Error::UnsupportedHypervisorUuid(u)),
+        }
+    }
+}
+
+const ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID: u32 = 0x8600ff01;
+
+fn query_vendor_hyp_call_uid() -> Uuid {
+    let args = [0u64; 17];
+    let res = hvc64(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, args);
+
+    // KVM's UUID of "28b46fb6-2ec5-11e9-a9ca-4b564d003a74" is generated by
+    // Uuid::from_u128() from an input value of
+    // 0x28b46fb6_2ec511e9_a9ca4b56_4d003a74. ARM's SMC calling convention
+    // (Document number ARM DEN 0028E) describes the UUID register mapping such
+    // that W0 contains bytes 0..3 of UUID, with byte 0 in lower order bits. In
+    // the KVM example, byte 0 of KVM's UUID (0x28) will be returned in the low
+    // 8-bits of W0, while byte 15 (0x74) will be returned in bits 31-24 of W3.
+    //
+    // `uuid` value derived below thus need to be byte-reversed before
+    // being used in Uuid::from_u128(). Alternately use Uuid::from_u128_le()
+    // to achieve the same.
+
+    let uuid = ((res[3] as u32 as u128) << 96)
+        | ((res[2] as u32 as u128) << 64)
+        | ((res[1] as u32 as u128) << 32)
+        | (res[0] as u32 as u128);
+
+    Uuid::from_u128_le(uuid)
+}
+
+fn detect_hypervisor() -> HypervisorBackend {
+    query_vendor_hyp_call_uid().try_into().expect("Failed to detect hypervisor")
+}
+
+/// Gets the hypervisor singleton.
+fn get_hypervisor() -> &'static dyn Hypervisor {
+    static HYPERVISOR: OnceBox<HypervisorBackend> = OnceBox::new();
+
+    HYPERVISOR.get_or_init(|| Box::new(detect_hypervisor())).get_hypervisor()
+}
+
+/// Gets the MMIO_GUARD hypervisor singleton, if any.
+pub fn get_mmio_guard() -> Option<&'static dyn MmioGuardedHypervisor> {
+    get_hypervisor().as_mmio_guard()
+}
+
+/// Gets the dynamic memory sharing hypervisor singleton, if any.
+pub fn get_mem_sharer() -> Option<&'static dyn MemSharingHypervisor> {
+    get_hypervisor().as_mem_sharer()
+}
+
+/// Gets the device assigning hypervisor singleton, if any.
+pub fn get_device_assigner() -> Option<&'static dyn DeviceAssigningHypervisor> {
+    get_hypervisor().as_device_assigner()
+}
diff --git a/vmbase/src/hyp/hypervisor/common.rs b/vmbase/src/hyp/hypervisor/common.rs
new file mode 100644
index 0000000..de564a8
--- /dev/null
+++ b/vmbase/src/hyp/hypervisor/common.rs
@@ -0,0 +1,88 @@
+// Copyright 2023, 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.
+
+//! This module regroups some common traits shared by all the hypervisors.
+
+use crate::hyp::{util::SIZE_4KB, Error, Result};
+
+/// Expected MMIO guard granule size, validated during MMIO guard initialization.
+pub const MMIO_GUARD_GRANULE_SIZE: usize = SIZE_4KB;
+
+/// Trait for the hypervisor.
+pub trait Hypervisor {
+    /// Returns the hypervisor's MMIO_GUARD implementation, if any.
+    fn as_mmio_guard(&self) -> Option<&dyn MmioGuardedHypervisor> {
+        None
+    }
+
+    /// Returns the hypervisor's dynamic memory sharing implementation, if any.
+    fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
+        None
+    }
+
+    /// Returns the hypervisor's device assigning implementation, if any.
+    fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+        None
+    }
+}
+
+pub trait MmioGuardedHypervisor {
+    /// Enrolls with the MMIO guard so that all MMIO will be blocked unless allow-listed with
+    /// `MmioGuardedHypervisor::map`.
+    fn enroll(&self) -> Result<()>;
+
+    /// Maps a page containing the given memory address to the hypervisor MMIO guard.
+    /// The page size corresponds to the MMIO guard granule size.
+    fn map(&self, addr: usize) -> Result<()>;
+
+    /// Unmaps a page containing the given memory address from the hypervisor MMIO guard.
+    /// The page size corresponds to the MMIO guard granule size.
+    fn unmap(&self, addr: usize) -> Result<()>;
+
+    /// Returns the MMIO guard granule size in bytes.
+    fn granule(&self) -> Result<usize>;
+
+    // TODO(ptosi): Fully move granule validation to client code.
+    /// Validates the MMIO guard granule size.
+    fn validate_granule(&self) -> Result<()> {
+        match self.granule()? {
+            MMIO_GUARD_GRANULE_SIZE => Ok(()),
+            granule => Err(Error::UnsupportedMmioGuardGranule(granule)),
+        }
+    }
+}
+
+pub trait MemSharingHypervisor {
+    /// Shares a region of memory with host, granting it read, write and execute permissions.
+    /// The size of the region is equal to the memory protection granule returned by
+    /// [`hyp_meminfo`].
+    fn share(&self, base_ipa: u64) -> Result<()>;
+
+    /// Revokes access permission from host to a memory region previously shared with
+    /// [`mem_share`]. The size of the region is equal to the memory protection granule returned by
+    /// [`hyp_meminfo`].
+    fn unshare(&self, base_ipa: u64) -> Result<()>;
+
+    /// Returns the memory protection granule size in bytes.
+    fn granule(&self) -> Result<usize>;
+}
+
+/// Device assigning hypervisor
+pub trait DeviceAssigningHypervisor {
+    /// Returns MMIO token.
+    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
+
+    /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
+    fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)>;
+}
diff --git a/vmbase/src/hyp/hypervisor/geniezone.rs b/vmbase/src/hyp/hypervisor/geniezone.rs
new file mode 100644
index 0000000..69ecf41
--- /dev/null
+++ b/vmbase/src/hyp/hypervisor/geniezone.rs
@@ -0,0 +1,158 @@
+// Copyright 2023, 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 GenieZone hypervisor.
+
+use core::fmt::{self, Display, Formatter};
+
+use super::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
+use crate::hyp::{util::page_address, Error, Result};
+
+use smccc::{
+    error::{positive_or_error_64, success_or_error_64},
+    hvc64,
+};
+use uuid::{uuid, Uuid};
+
+pub(super) struct GeniezoneHypervisor;
+
+const ARM_SMCCC_GZVM_FUNC_HYP_MEMINFO: u32 = 0xc6000002;
+const ARM_SMCCC_GZVM_FUNC_MEM_SHARE: u32 = 0xc6000003;
+const ARM_SMCCC_GZVM_FUNC_MEM_UNSHARE: u32 = 0xc6000004;
+
+const VENDOR_HYP_GZVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
+const VENDOR_HYP_GZVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+const VENDOR_HYP_GZVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+
+impl GeniezoneHypervisor {
+    // We generate this uuid by ourselves to identify GenieZone hypervisor
+    // and share the same identification along with guest VMs.
+    // The previous uuid was removed due to duplication elsewhere.
+    pub const UUID: Uuid = uuid!("7e134ed0-3b82-488d-8cee-69c19211dbe7");
+}
+
+/// Error from a GenieZone HVC call.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum GeniezoneError {
+    /// The call is not supported by the implementation.
+    NotSupported,
+    /// The call is not required to implement.
+    NotRequired,
+    /// One of the call parameters has a invalid value.
+    InvalidParameter,
+    /// There was an unexpected return value.
+    Unknown(i64),
+}
+
+impl From<i64> for GeniezoneError {
+    fn from(value: i64) -> Self {
+        match value {
+            -1 => GeniezoneError::NotSupported,
+            -2 => GeniezoneError::NotRequired,
+            -3 => GeniezoneError::InvalidParameter,
+            _ => GeniezoneError::Unknown(value),
+        }
+    }
+}
+
+impl From<i32> for GeniezoneError {
+    fn from(value: i32) -> Self {
+        i64::from(value).into()
+    }
+}
+
+impl Display for GeniezoneError {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match self {
+            Self::NotSupported => write!(f, "GenieZone call not supported"),
+            Self::NotRequired => write!(f, "GenieZone call not required"),
+            Self::InvalidParameter => write!(f, "GenieZone call received invalid value"),
+            Self::Unknown(e) => write!(f, "Unknown return value from GenieZone {} ({0:#x})", e),
+        }
+    }
+}
+
+impl Hypervisor for GeniezoneHypervisor {
+    fn as_mmio_guard(&self) -> Option<&dyn MmioGuardedHypervisor> {
+        Some(self)
+    }
+
+    fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
+        Some(self)
+    }
+}
+
+impl MmioGuardedHypervisor for GeniezoneHypervisor {
+    fn enroll(&self) -> Result<()> {
+        let args = [0u64; 17];
+        match success_or_error_64(hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
+            Ok(()) => Ok(()),
+            Err(GeniezoneError::NotSupported) | Err(GeniezoneError::NotRequired) => {
+                Err(Error::MmioGuardNotSupported)
+            }
+            Err(e) => Err(Error::GeniezoneError(e, VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID)),
+        }
+    }
+
+    fn map(&self, addr: usize) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = page_address(addr);
+
+        checked_hvc64_expect_zero(VENDOR_HYP_GZVM_MMIO_GUARD_MAP_FUNC_ID, args)
+    }
+
+    fn unmap(&self, addr: usize) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = page_address(addr);
+
+        checked_hvc64_expect_zero(VENDOR_HYP_GZVM_MMIO_GUARD_UNMAP_FUNC_ID, args)
+    }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
+        Ok(granule.try_into().unwrap())
+    }
+}
+
+impl MemSharingHypervisor for GeniezoneHypervisor {
+    fn share(&self, base_ipa: u64) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+
+        checked_hvc64_expect_zero(ARM_SMCCC_GZVM_FUNC_MEM_SHARE, args)
+    }
+
+    fn unshare(&self, base_ipa: u64) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+
+        checked_hvc64_expect_zero(ARM_SMCCC_GZVM_FUNC_MEM_UNSHARE, args)
+    }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(ARM_SMCCC_GZVM_FUNC_HYP_MEMINFO, args)?;
+        Ok(granule.try_into().unwrap())
+    }
+}
+
+fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
+    success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::GeniezoneError(e, function))
+}
+
+fn checked_hvc64(function: u32, args: [u64; 17]) -> Result<u64> {
+    positive_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::GeniezoneError(e, function))
+}
diff --git a/vmbase/src/hyp/hypervisor/gunyah.rs b/vmbase/src/hyp/hypervisor/gunyah.rs
new file mode 100644
index 0000000..45c01bf
--- /dev/null
+++ b/vmbase/src/hyp/hypervisor/gunyah.rs
@@ -0,0 +1,10 @@
+use super::common::Hypervisor;
+use uuid::{uuid, Uuid};
+
+pub(super) struct GunyahHypervisor;
+
+impl GunyahHypervisor {
+    pub const UUID: Uuid = uuid!("c1d58fcd-a453-5fdb-9265-ce36673d5f14");
+}
+
+impl Hypervisor for GunyahHypervisor {}
diff --git a/vmbase/src/hyp/hypervisor/kvm.rs b/vmbase/src/hyp/hypervisor/kvm.rs
new file mode 100644
index 0000000..8b59234
--- /dev/null
+++ b/vmbase/src/hyp/hypervisor/kvm.rs
@@ -0,0 +1,196 @@
+// Copyright 2022, 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 core::fmt::{self, Display, Formatter};
+
+use super::{DeviceAssigningHypervisor, Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
+use crate::hyp::{util::page_address, Error, Result};
+
+use smccc::{
+    error::{positive_or_error_64, success_or_error_32, success_or_error_64},
+    hvc64,
+};
+use uuid::{uuid, Uuid};
+
+/// 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 {
+            -1 => KvmError::NotSupported,
+            -3 => 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),
+        }
+    }
+}
+
+const ARM_SMCCC_KVM_FUNC_HYP_MEMINFO: u32 = 0xc6000002;
+const ARM_SMCCC_KVM_FUNC_MEM_SHARE: u32 = 0xc6000003;
+const ARM_SMCCC_KVM_FUNC_MEM_UNSHARE: u32 = 0xc6000004;
+
+const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID: u32 = 0xc6000006;
+const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+
+const VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: u32 = 0xc6000012;
+const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc6000013;
+
+pub(super) struct RegularKvmHypervisor;
+
+impl RegularKvmHypervisor {
+    // Based on ARM_SMCCC_VENDOR_HYP_UID_KVM_REG values listed in Linux kernel source:
+    // https://github.com/torvalds/linux/blob/master/include/linux/arm-smccc.h
+    pub(super) const UUID: Uuid = uuid!("28b46fb6-2ec5-11e9-a9ca-4b564d003a74");
+}
+
+impl Hypervisor for RegularKvmHypervisor {}
+
+pub(super) struct ProtectedKvmHypervisor;
+
+impl Hypervisor for ProtectedKvmHypervisor {
+    fn as_mmio_guard(&self) -> Option<&dyn MmioGuardedHypervisor> {
+        Some(self)
+    }
+
+    fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
+        Some(self)
+    }
+
+    fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+        Some(self)
+    }
+}
+
+impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
+    fn enroll(&self) -> Result<()> {
+        let args = [0u64; 17];
+        match success_or_error_64(hvc64(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
+            Ok(()) => Ok(()),
+            Err(KvmError::NotSupported) => Err(Error::MmioGuardNotSupported),
+            Err(e) => Err(Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID)),
+        }
+    }
+
+    fn map(&self, addr: usize) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = page_address(addr);
+
+        // TODO(b/277859415): pKVM returns a i32 instead of a i64 in T.
+        // Drop this hack once T reaches EoL.
+        success_or_error_32(hvc64(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args)[0] as u32)
+            .map_err(|e| Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID))
+    }
+
+    fn unmap(&self, addr: usize) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = page_address(addr);
+
+        // TODO(b/277860860): pKVM returns NOT_SUPPORTED for SUCCESS in T.
+        // Drop this hack once T reaches EoL.
+        match success_or_error_64(hvc64(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args)[0]) {
+            Err(KvmError::NotSupported) | Ok(_) => Ok(()),
+            Err(e) => Err(Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID)),
+        }
+    }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
+        Ok(granule.try_into().unwrap())
+    }
+}
+
+impl MemSharingHypervisor for ProtectedKvmHypervisor {
+    fn share(&self, base_ipa: u64) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+
+        checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_SHARE, args)
+    }
+
+    fn unshare(&self, base_ipa: u64) -> Result<()> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+
+        checked_hvc64_expect_zero(ARM_SMCCC_KVM_FUNC_MEM_UNSHARE, args)
+    }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(ARM_SMCCC_KVM_FUNC_HYP_MEMINFO, args)?;
+        Ok(granule.try_into().unwrap())
+    }
+}
+
+impl DeviceAssigningHypervisor for ProtectedKvmHypervisor {
+    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+        args[1] = size;
+
+        let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID, args)?;
+        Ok(ret[0])
+    }
+
+    fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)> {
+        let mut args = [0u64; 17];
+        args[0] = pviommu_id;
+        args[1] = vsid;
+
+        let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID, args)?;
+        Ok((ret[0], ret[1]))
+    }
+}
+
+fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
+    success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
+}
+
+fn checked_hvc64(function: u32, args: [u64; 17]) -> Result<u64> {
+    positive_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
+}
+
+fn checked_hvc64_expect_results(function: u32, args: [u64; 17]) -> Result<[u64; 17]> {
+    let [ret, results @ ..] = hvc64(function, args);
+    success_or_error_64(ret).map_err(|e| Error::KvmError(e, function))?;
+    Ok(results)
+}
diff --git a/vmbase/src/hyp/util.rs b/vmbase/src/hyp/util.rs
new file mode 100644
index 0000000..56f94fd
--- /dev/null
+++ b/vmbase/src/hyp/util.rs
@@ -0,0 +1,22 @@
+// Copyright 2023, 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.
+
+//! Utility functions.
+
+pub(crate) const SIZE_4KB: usize = 4 << 10;
+
+/// Computes the low memory page address of the 4KiB page containing a given address.
+pub(crate) fn page_address(addr: usize) -> u64 {
+    (addr & !(SIZE_4KB - 1)).try_into().unwrap()
+}
diff --git a/vmbase/src/lib.rs b/vmbase/src/lib.rs
index 431e899..630834b 100644
--- a/vmbase/src/lib.rs
+++ b/vmbase/src/lib.rs
@@ -26,6 +26,7 @@
 pub mod fdt;
 pub mod heap;
 mod hvc;
+pub mod hyp;
 pub mod layout;
 pub mod linker;
 pub mod logger;
diff --git a/vmbase/src/memory/error.rs b/vmbase/src/memory/error.rs
index 273db56..1af8f8c 100644
--- a/vmbase/src/memory/error.rs
+++ b/vmbase/src/memory/error.rs
@@ -16,6 +16,8 @@
 
 use core::fmt;
 
+use crate::hyp;
+
 /// Errors for MemoryTracker operations.
 #[derive(Debug, Clone)]
 pub enum MemoryTrackerError {
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index dd433d4..8fda7a6 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -20,6 +20,7 @@
 use super::util::{page_4kb_of, virt_to_phys};
 use crate::dsb;
 use crate::exceptions::HandleExceptionError;
+use crate::hyp::{self, get_mem_sharer, get_mmio_guard, MMIO_GUARD_GRANULE_SIZE};
 use crate::util::RangeExt as _;
 use aarch64_paging::paging::{
     Attributes, Descriptor, MemoryRegion as VaRange, VirtualAddress, BITS_PER_LEVEL, PAGE_SIZE,
@@ -35,7 +36,6 @@
 use core::ops::Range;
 use core::ptr::NonNull;
 use core::result;
-use hyp::{get_mem_sharer, get_mmio_guard, MMIO_GUARD_GRANULE_SIZE};
 use log::{debug, error, trace};
 use once_cell::race::OnceBox;
 use spin::mutex::SpinMutex;