hypervisor: Implement gunyah

Implement base gunyah abstractions.

Bindings are based off aosp/2475305/9.

Cherry-pick notes: Fixed license headers and uppercased bindgen constant
to workaround "constant in pattern ... should have an upper case name"
warning that can't be disabled (will need to adjust the bindgen script
later to match).

BUG=b:232360323

Change-Id: I4815128ce827112b25add87ac11e28868b4cdb46
Co-developed-by: Sreenad Menon <quic_sreemeno@quicinc.com>
Signed-off-by: Sreenad Menon <quic_sreemeno@quicinc.com>
Signed-off-by: Elliot Berman <quic_eberman@quicinc.com>
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4404217
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Elliot Berman 2022-10-12 14:21:32 -07:00 committed by crosvm LUCI
parent 3cfc7c260b
commit 9110f097f0
6 changed files with 1406 additions and 0 deletions

View file

@ -0,0 +1,297 @@
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use cros_fdt::FdtWriter;
use libc::ENOENT;
use libc::ENOTSUP;
use vm_memory::GuestAddress;
use vm_memory::MemoryRegionInformation;
use vm_memory::MemoryRegionOptions;
use vm_memory::MemoryRegionPurpose;
use base::error;
use base::Error;
use base::Result;
use crate::Hypervisor;
use crate::PsciVersion;
use crate::VcpuAArch64;
use crate::VcpuRegAArch64;
use crate::VmAArch64;
use crate::PSCI_0_2;
use super::GunyahVcpu;
use super::GunyahVm;
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001;
const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
fn fdt_create_shm_device(
fdt: &mut FdtWriter,
index: u32,
guest_addr: GuestAddress,
options: MemoryRegionOptions,
) -> cros_fdt::Result<()> {
let shm_name = format!("shm-{:x}", index);
let shm_node = fdt.begin_node(&shm_name)?;
fdt.property_string("vdevice-type", "shm")?;
fdt.property_null("peer-default")?;
if options.purpose == MemoryRegionPurpose::StaticSwiotlbRegion {
fdt.property_string("push-compatible", "restricted-dma-pool")?;
}
fdt.property_u64("dma_base", 0)?;
let mem_node = fdt.begin_node("memory")?;
fdt.property_u32("label", index)?;
fdt.property_u32("#address-cells", 2)?;
fdt.property_u64("base", guest_addr.offset() as u64)?;
fdt.end_node(mem_node)?;
fdt.end_node(shm_node)
}
impl VmAArch64 for GunyahVm {
fn get_hypervisor(&self) -> &dyn Hypervisor {
&self.gh
}
fn load_protected_vm_firmware(
&mut self,
fw_addr: GuestAddress,
fw_max_size: u64,
) -> Result<()> {
self.set_protected_vm_firmware_ipa(fw_addr, fw_max_size)
}
fn create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>> {
Ok(Box::new(GunyahVm::create_vcpu(self, id)?))
}
fn create_fdt(
&self,
fdt: &mut FdtWriter,
phandles: &BTreeMap<&str, u32>,
) -> cros_fdt::Result<()> {
let top_node = fdt.begin_node("gunyah-vm-config")?;
fdt.property_string("image-name", "crosvm-vm")?;
fdt.property_string("os-type", "linux")?;
let memory_node = fdt.begin_node("memory")?;
fdt.property_u32("#address-cells", 2)?;
fdt.property_u32("#size-cells", 2)?;
let mut base_set = false;
let mut firmware_set = false;
self.guest_mem.with_regions(
|MemoryRegionInformation {
guest_addr,
options,
..
}| {
match options.purpose {
MemoryRegionPurpose::GuestMemoryRegion => {
// Assume first GuestMemoryRegion contains the payload
if !base_set {
base_set = true;
fdt.property_u64("base-address", guest_addr.offset())
} else {
Ok(())
}
}
MemoryRegionPurpose::ProtectedFirmwareRegion => {
if firmware_set {
// Should only have one protected firmware memory region.
error!("Multiple ProtectedFirmwareRegions unexpected.");
unreachable!()
}
firmware_set = true;
fdt.property_u64("firmware-address", guest_addr.offset())
}
_ => Ok(()),
}
},
)?;
fdt.end_node(memory_node)?;
let interrupts_node = fdt.begin_node("interrupts")?;
fdt.property_u32("config", *phandles.get("intc").unwrap())?;
fdt.end_node(interrupts_node)?;
let vcpus_node = fdt.begin_node("vcpus")?;
fdt.property_string("affinity", "proxy")?;
fdt.end_node(vcpus_node)?;
let vdev_node = fdt.begin_node("vdevices")?;
fdt.property_string("generate", "/hypervisor")?;
for irq in self.routes.lock().iter() {
let bell_name = format!("bell-{:x}", irq.irq);
let bell_node = fdt.begin_node(&bell_name)?;
fdt.property_string("vdevice-type", "doorbell")?;
let path_name = format!("/hypervisor/bell-{:x}", irq.irq);
fdt.property_string("generate", &path_name)?;
fdt.property_u32("label", irq.irq)?;
fdt.property_null("peer-default")?;
fdt.property_null("source-can-clear")?;
let interrupt_type = if irq.level {
IRQ_TYPE_LEVEL_HIGH
} else {
IRQ_TYPE_EDGE_RISING
};
let interrupts = [GIC_FDT_IRQ_TYPE_SPI, irq.irq, interrupt_type];
fdt.property_array_u32("interrupts", &interrupts)?;
fdt.end_node(bell_node)?;
}
let mut base_set = false;
self.guest_mem.with_regions(
|MemoryRegionInformation {
index,
guest_addr,
options,
..
}| {
let create_shm_node = match options.purpose {
MemoryRegionPurpose::GuestMemoryRegion => {
// Assume first GuestMemoryRegion contains the payload
// This memory region is described by the "base-address" property
// and doesn't get re-described as a separate shm node.
let ret = base_set;
base_set = true;
ret
}
// Described by the "firmware-address" property
MemoryRegionPurpose::ProtectedFirmwareRegion => false,
MemoryRegionPurpose::StaticSwiotlbRegion => true,
};
if create_shm_node {
fdt_create_shm_device(fdt, index.try_into().unwrap(), guest_addr, options)?;
}
Ok(())
},
)?;
fdt.end_node(vdev_node)?;
fdt.end_node(top_node)?;
Ok(())
}
fn init_arch(
&self,
payload_entry_address: GuestAddress,
fdt_address: GuestAddress,
fdt_size: usize,
) -> Result<()> {
// Gunyah initializes the PC to be the payload entry (except for protected VMs)
// and assumes that the image is loaded at the beginning of the "primary"
// memory parcel (region). This parcel contains both DTB and kernel Image, so
// make sure that DTB and payload are in the same memory region and that
// payload is at the start of that region.
let (dtb_mapping, _, dtb_obj_offset) = self
.guest_mem
.find_region(fdt_address)
.map_err(|_| Error::new(ENOENT))?;
let (payload_mapping, payload_offset, payload_obj_offset) = self
.guest_mem
.find_region(payload_entry_address)
.map_err(|_| Error::new(ENOENT))?;
if !std::ptr::eq(dtb_mapping, payload_mapping) || dtb_obj_offset != payload_obj_offset {
panic!("DTB and payload are not part of same memory region.");
}
if payload_offset != 0 {
panic!("Payload offset must be zero");
}
self.set_dtb_config(fdt_address, fdt_size)?;
self.start()?;
Ok(())
}
}
impl VcpuAArch64 for GunyahVcpu {
fn init(&self, _features: &[crate::VcpuFeature]) -> Result<()> {
Ok(())
}
fn init_pmu(&self, _irq: u64) -> Result<()> {
Err(Error::new(ENOTSUP))
}
fn has_pvtime_support(&self) -> bool {
false
}
fn init_pvtime(&self, _pvtime_ipa: u64) -> Result<()> {
Err(Error::new(ENOTSUP))
}
fn set_one_reg(&self, _reg_id: VcpuRegAArch64, _data: u64) -> Result<()> {
unimplemented!()
}
fn get_one_reg(&self, _reg_id: VcpuRegAArch64) -> Result<u64> {
Err(Error::new(ENOTSUP))
}
fn get_psci_version(&self) -> Result<PsciVersion> {
Ok(PSCI_0_2)
}
#[cfg(feature = "gdb")]
fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
Err(Error::new(ENOTSUP))
}
#[cfg(feature = "gdb")]
fn set_gdb_registers(
&self,
_regs: &<gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers,
) -> Result<()> {
Err(Error::new(ENOTSUP))
}
#[cfg(feature = "gdb")]
fn get_gdb_registers(
&self,
_regs: &mut <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::Registers,
) -> Result<()> {
Err(Error::new(ENOTSUP))
}
#[cfg(feature = "gdb")]
fn get_max_hw_bps(&self) -> Result<usize> {
Err(Error::new(ENOTSUP))
}
#[cfg(feature = "gdb")]
fn set_gdb_register(
&self,
_reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId,
_data: &[u8],
) -> Result<()> {
Err(Error::new(ENOTSUP))
}
#[cfg(feature = "gdb")]
fn get_gdb_register(
&self,
_reg: <gdbstub_arch::aarch64::AArch64 as gdbstub::arch::Arch>::RegId,
_data: &mut [u8],
) -> Result<usize> {
Err(Error::new(ENOTSUP))
}
}

View file

@ -0,0 +1,45 @@
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Bindings for the Linux Gunyah API.
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use base::ioctl_io_nr;
use base::ioctl_iow_nr;
// generated with gunyah_sys/bindgen.sh
pub mod bindings;
pub use bindings::*;
// These ioctls are commonly defined on all/multiple platforms.
ioctl_io_nr!(GH_CREATE_VM, GH_IOCTL_TYPE, 0x0);
ioctl_iow_nr!(
GH_VM_SET_USER_MEM_REGION,
GH_IOCTL_TYPE,
0x1,
gh_userspace_memory_region
);
ioctl_iow_nr!(GH_VM_SET_DTB_CONFIG, GH_IOCTL_TYPE, 0x2, gh_vm_dtb_config);
ioctl_io_nr!(GH_VM_START, GH_IOCTL_TYPE, 0x3);
ioctl_iow_nr!(GH_VM_ADD_FUNCTION, GH_IOCTL_TYPE, 0x4, gh_fn_desc);
ioctl_io_nr!(GH_VCPU_RUN, GH_IOCTL_TYPE, 0x5);
ioctl_io_nr!(GH_VCPU_MMAP_SIZE, GH_IOCTL_TYPE, 0x6);
// Special bindings for Android Common Kernel
pub const GH_ANDROID_IOCTL_TYPE: u8 = 65u8;
ioctl_iow_nr!(
GH_VM_ANDROID_LEND_USER_MEM,
GH_ANDROID_IOCTL_TYPE,
0x11,
gh_userspace_memory_region
);
ioctl_iow_nr!(
GH_VM_ANDROID_SET_FW_CONFIG,
GH_ANDROID_IOCTL_TYPE,
0x12,
gh_vm_firmware_config
);

View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Regenerate gunyah_sys bindgen bindings.
set -euo pipefail
# assume in hypervisor/src/gunyah/gunyah_sys
cd "$(dirname "${BASH_SOURCE[0]}")/../../../.."
source tools/impl/bindgen-common.sh
# use local copy until gunyah.h is available in chromium monorepo
bindgen_generate \
--blocklist-item='__kernel.*' \
--blocklist-item='__BITS_PER_LONG' \
--blocklist-item='__FD_SETSIZE' \
--blocklist-item='_?IOC.*' \
"hypervisor/src/gunyah/gunyah_sys/gunyah.h" \
-- \
-isystem "${BINDGEN_LINUX_ARM64_HEADERS}/include" \
| replace_linux_int_types \
> hypervisor/src/gunyah/gunyah_sys/bindings.rs

View file

@ -0,0 +1,151 @@
/* automatically generated by tools/bindgen-all-the-things */
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::upper_case_acronyms)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
pub const GH_IOCTL_TYPE: u8 = 71u8;
pub const GH_MEM_ALLOW_READ: u32 = 1;
pub const GH_MEM_ALLOW_WRITE: u32 = 2;
pub const GH_MEM_ALLOW_EXEC: u32 = 4;
pub const GH_FN_VCPU: u32 = 1;
pub const GH_FN_IRQFD: u32 = 2;
pub const GH_FN_IOEVENTFD: u32 = 3;
pub const GH_FN_MAX_ARG_SIZE: u32 = 256;
pub const GH_IRQFD_LEVEL: u32 = 1;
pub const GH_IOEVENTFD_DATAMATCH: u32 = 1;
pub const GH_VM_MAX_EXIT_REASON_SIZE: u32 = 8;
pub const GH_VCPU_EXIT_UNKNOWN: u32 = 0;
pub const GH_VCPU_EXIT_MMIO: u32 = 1;
pub const GH_VCPU_EXIT_STATUS: u32 = 2;
pub const GH_ANDROID_IOCTL_TYPE: u8 = 65u8;
pub type __le16 = u16;
pub type __be16 = u16;
pub type __le32 = u32;
pub type __be32 = u32;
pub type __le64 = u64;
pub type __be64 = u64;
pub type __sum16 = u16;
pub type __wsum = u32;
pub type __poll_t = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_userspace_memory_region {
pub label: u32,
pub flags: u32,
pub guest_phys_addr: u64,
pub memory_size: u64,
pub userspace_addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_vm_dtb_config {
pub guest_phys_addr: u64,
pub size: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_fn_vcpu_arg {
pub id: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_fn_irqfd_arg {
pub fd: u32,
pub label: u32,
pub flags: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_fn_ioeventfd_arg {
pub datamatch: u64,
pub addr: u64,
pub len: u32,
pub fd: i32,
pub flags: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_fn_desc {
pub type_: u32,
pub arg_size: u32,
pub arg: u64,
}
pub const GH_VM_STATUS_GH_VM_STATUS_LOAD_FAILED: gh_vm_status = 1;
pub const GH_VM_STATUS_GH_VM_STATUS_EXITED: gh_vm_status = 2;
pub const GH_VM_STATUS_GH_VM_STATUS_CRASHED: gh_vm_status = 3;
pub type gh_vm_status = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_vm_exit_info {
pub type_: u16,
pub padding: u16,
pub reason_size: u32,
pub reason: [u8; 8usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct gh_vcpu_run {
pub immediate_exit: u8,
pub padding: [u8; 7usize],
pub exit_reason: u32,
pub __bindgen_anon_1: gh_vcpu_run__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union gh_vcpu_run__bindgen_ty_1 {
pub mmio: gh_vcpu_run__bindgen_ty_1__bindgen_ty_1,
pub status: gh_vcpu_run__bindgen_ty_1__bindgen_ty_2,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_vcpu_run__bindgen_ty_1__bindgen_ty_1 {
pub phys_addr: u64,
pub data: [u8; 8usize],
pub len: u32,
pub is_write: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct gh_vcpu_run__bindgen_ty_1__bindgen_ty_2 {
pub status: gh_vm_status,
pub exit_info: gh_vm_exit_info,
}
impl Default for gh_vcpu_run__bindgen_ty_1__bindgen_ty_2 {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
impl Default for gh_vcpu_run__bindgen_ty_1 {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
impl Default for gh_vcpu_run {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct gh_vm_firmware_config {
pub guest_phys_addr: u64,
pub size: u64,
}

View file

@ -0,0 +1,883 @@
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
mod aarch64;
mod gunyah_sys;
use gunyah_sys::*;
use std::collections::HashSet;
use std::cell::RefCell;
use std::cmp::min;
use std::cmp::Reverse;
use std::collections::BTreeMap;
use std::collections::BinaryHeap;
use std::ffi::CString;
use std::mem::ManuallyDrop;
use std::os::raw::c_ulong;
use std::os::unix::prelude::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::mem::size_of;
use crate::*;
use base::errno_result;
use base::info;
use base::ioctl;
use base::ioctl_with_ref;
use base::ioctl_with_val;
use base::pagesize;
use base::sys::BlockedSignal;
use base::warn;
use base::Error;
use base::FromRawDescriptor;
use base::MemoryMapping;
use base::MemoryMappingBuilder;
use base::MemoryMappingBuilderUnix;
use base::MmapError;
use base::RawDescriptor;
use libc::open;
use libc::EBUSY;
use libc::EFAULT;
use libc::EINVAL;
use libc::EIO;
use libc::ENOENT;
use libc::ENOSPC;
use libc::ENOTSUP;
use libc::EOVERFLOW;
use libc::O_CLOEXEC;
use libc::O_RDWR;
use sync::Mutex;
use vm_memory::MemoryRegionInformation;
use vm_memory::MemoryRegionPurpose;
pub struct Gunyah {
gunyah: SafeDescriptor,
}
impl AsRawDescriptor for Gunyah {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.gunyah.as_raw_descriptor()
}
}
impl Gunyah {
pub fn new_with_path(device_path: &Path) -> Result<Gunyah> {
// Open calls are safe because we give a nul-terminated string and verify the result.
let c_path = CString::new(device_path.as_os_str().as_bytes()).unwrap();
let ret = unsafe { open(c_path.as_ptr(), O_RDWR | O_CLOEXEC) };
if ret < 0 {
return errno_result();
}
// Safe because we verify that ret is valid and we own the fd.
Ok(Gunyah {
gunyah: unsafe { SafeDescriptor::from_raw_descriptor(ret) },
})
}
pub fn new() -> Result<Gunyah> {
Gunyah::new_with_path(&PathBuf::from("/dev/gunyah"))
}
}
impl Hypervisor for Gunyah {
fn try_clone(&self) -> Result<Self>
where
Self: Sized,
{
Ok(Gunyah {
gunyah: self.gunyah.try_clone()?,
})
}
fn check_capability(&self, cap: HypervisorCap) -> bool {
match cap {
HypervisorCap::UserMemory => true,
HypervisorCap::ArmPmuV3 => false,
HypervisorCap::ImmediateExit => true,
HypervisorCap::StaticSwiotlbAllocationRequired => true,
HypervisorCap::HypervisorInitializedBootContext => true,
HypervisorCap::S390UserSigp | HypervisorCap::TscDeadlineTimer => false,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
HypervisorCap::Xcrs | HypervisorCap::CalibratedTscLeafRequired => false,
}
}
}
unsafe fn android_lend_user_memory_region(
vm: &SafeDescriptor,
slot: MemSlot,
read_only: bool,
guest_addr: u64,
memory_size: u64,
userspace_addr: *mut u8,
) -> Result<()> {
let mut flags = 0;
flags |= GH_MEM_ALLOW_READ | GH_MEM_ALLOW_EXEC;
if !read_only {
flags |= GH_MEM_ALLOW_WRITE;
}
let region = gh_userspace_memory_region {
label: slot,
flags,
guest_phys_addr: guest_addr,
memory_size,
userspace_addr: userspace_addr as u64,
};
let ret = ioctl_with_ref(vm, GH_VM_ANDROID_LEND_USER_MEM(), &region);
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
// Wrapper around GH_SET_USER_MEMORY_REGION ioctl, which creates, modifies, or deletes a mapping
// from guest physical to host user pages.
//
// Safe when the guest regions are guaranteed not to overlap.
unsafe fn set_user_memory_region(
vm: &SafeDescriptor,
slot: MemSlot,
read_only: bool,
guest_addr: u64,
memory_size: u64,
userspace_addr: *mut u8,
) -> Result<()> {
let mut flags = 0;
flags |= GH_MEM_ALLOW_READ | GH_MEM_ALLOW_EXEC;
if !read_only {
flags |= GH_MEM_ALLOW_WRITE;
}
let region = gh_userspace_memory_region {
label: slot,
flags,
guest_phys_addr: guest_addr,
memory_size,
userspace_addr: userspace_addr as u64,
};
let ret = ioctl_with_ref(vm, GH_VM_SET_USER_MEM_REGION(), &region);
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
#[derive(PartialEq, Eq, Hash)]
pub struct GunyahIrqRoute {
irq: u32,
level: bool,
}
pub struct GunyahVm {
gh: Gunyah,
vm: SafeDescriptor,
guest_mem: GuestMemory,
mem_regions: Arc<Mutex<BTreeMap<MemSlot, (Box<dyn MappedRegion>, GuestAddress)>>>,
/// A min heap of MemSlot numbers that were used and then removed and can now be re-used
mem_slot_gaps: Arc<Mutex<BinaryHeap<Reverse<MemSlot>>>>,
routes: Arc<Mutex<HashSet<GunyahIrqRoute>>>,
hv_cfg: crate::Config,
}
impl AsRawDescriptor for GunyahVm {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.vm.as_raw_descriptor()
}
}
impl GunyahVm {
pub fn new(gh: &Gunyah, guest_mem: GuestMemory, cfg: Config) -> Result<GunyahVm> {
// Safe because we know gunyah is a real gunyah fd as this module is the only one that can
// make Gunyah objects.
let ret = unsafe { ioctl_with_val(gh, GH_CREATE_VM(), 0 as c_ulong) };
if ret < 0 {
return errno_result();
}
// Safe because we verify that ret is valid and we own the fd.
let vm_descriptor = unsafe { SafeDescriptor::from_raw_descriptor(ret) };
guest_mem.with_regions(
|MemoryRegionInformation {
index,
guest_addr,
size,
host_addr,
options,
..
}| {
let lend = if cfg.protection_type.isolates_memory() {
match options.purpose {
MemoryRegionPurpose::GuestMemoryRegion => true,
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
MemoryRegionPurpose::ProtectedFirmwareRegion => true,
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
MemoryRegionPurpose::StaticSwiotlbRegion => false,
}
} else {
false
};
if lend {
unsafe {
// Safe because the guest regions are guarnteed not to overlap.
android_lend_user_memory_region(
&vm_descriptor,
index as MemSlot,
false,
guest_addr.offset(),
size.try_into().unwrap(),
host_addr as *mut u8,
)
}
} else {
unsafe {
// Safe because the guest regions are guarnteed not to overlap.
set_user_memory_region(
&vm_descriptor,
index as MemSlot,
false,
guest_addr.offset(),
size.try_into().unwrap(),
host_addr as *mut u8,
)
}
}
},
)?;
Ok(GunyahVm {
gh: gh.try_clone()?,
vm: vm_descriptor,
guest_mem,
mem_regions: Arc::new(Mutex::new(BTreeMap::new())),
mem_slot_gaps: Arc::new(Mutex::new(BinaryHeap::new())),
routes: Arc::new(Mutex::new(HashSet::new())),
hv_cfg: cfg,
})
}
fn create_vcpu(&self, id: usize) -> Result<GunyahVcpu> {
let gh_fn_vcpu_arg = gh_fn_vcpu_arg {
id: id.try_into().unwrap(),
};
let function_desc = gh_fn_desc {
type_: GH_FN_VCPU,
arg_size: size_of::<gh_fn_vcpu_arg>() as u32,
// Safe because kernel is expecting pointer with non-zero arg_size
arg: &gh_fn_vcpu_arg as *const gh_fn_vcpu_arg as u64,
};
// Safe because we know that our file is a VM fd and we verify the return result.
let fd = unsafe { ioctl_with_ref(self, GH_VM_ADD_FUNCTION(), &function_desc) };
if fd < 0 {
return errno_result();
}
// Wrap the vcpu now in case the following ? returns early. This is safe because we verified
// the value of the fd and we own the fd.
let vcpu = unsafe { SafeDescriptor::from_raw_descriptor(fd) };
// Safe because we know this is a Gunyah VCPU
let res = unsafe { ioctl(&vcpu, GH_VCPU_MMAP_SIZE()) };
if res < 0 {
return errno_result();
}
let run_mmap_size = res as usize;
let run_mmap = MemoryMappingBuilder::new(run_mmap_size)
.from_descriptor(&vcpu)
.build()
.map_err(|_| Error::new(ENOSPC))?;
Ok(GunyahVcpu {
vm: self.vm.try_clone()?,
vcpu,
id,
run_mmap,
vcpu_run_handle_fingerprint: Default::default(),
})
}
pub fn register_irqfd(&self, label: u32, evt: &Event, level: bool) -> Result<()> {
let gh_fn_irqfd_arg = gh_fn_irqfd_arg {
fd: evt.as_raw_descriptor() as u32,
label,
flags: if level { GH_IRQFD_LEVEL } else { 0 },
..Default::default()
};
let function_desc = gh_fn_desc {
type_: GH_FN_IRQFD,
arg_size: size_of::<gh_fn_irqfd_arg>() as u32,
// Safe because kernel is expecting pointer with non-zero arg_size
arg: &gh_fn_irqfd_arg as *const gh_fn_irqfd_arg as u64,
};
let ret = unsafe { ioctl_with_ref(self, GH_VM_ADD_FUNCTION(), &function_desc) };
if ret == 0 {
self.routes
.lock()
.insert(GunyahIrqRoute { irq: label, level });
Ok(())
} else {
errno_result()
}
}
pub fn unregister_irqfd(&self, _label: u32, _evt: &Event) -> Result<()> {
unimplemented!()
}
pub fn try_clone(&self) -> Result<Self>
where
Self: Sized,
{
Ok(GunyahVm {
gh: self.gh.try_clone()?,
vm: self.vm.try_clone()?,
guest_mem: self.guest_mem.clone(),
mem_regions: self.mem_regions.clone(),
mem_slot_gaps: self.mem_slot_gaps.clone(),
routes: self.routes.clone(),
hv_cfg: self.hv_cfg,
})
}
fn set_dtb_config(&self, fdt_address: GuestAddress, fdt_size: usize) -> Result<()> {
let dtb_config = gh_vm_dtb_config {
guest_phys_addr: fdt_address.offset(),
size: fdt_size.try_into().unwrap(),
};
// Safe because we know this is a Gunyah VM
let ret = unsafe { ioctl_with_ref(self, GH_VM_SET_DTB_CONFIG(), &dtb_config) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
fn set_protected_vm_firmware_ipa(&self, fw_addr: GuestAddress, fw_size: u64) -> Result<()> {
let fw_config = gh_vm_firmware_config {
guest_phys_addr: fw_addr.offset(),
size: fw_size,
};
// Safe because we know this is a Gunyah VM
let ret = unsafe { ioctl_with_ref(self, GH_VM_ANDROID_SET_FW_CONFIG(), &fw_config) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
fn start(&self) -> Result<()> {
let ret = unsafe { ioctl(self, GH_VM_START()) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
}
impl Vm for GunyahVm {
fn try_clone(&self) -> Result<Self>
where
Self: Sized,
{
Ok(GunyahVm {
gh: self.gh.try_clone()?,
vm: self.vm.try_clone()?,
guest_mem: self.guest_mem.clone(),
mem_regions: self.mem_regions.clone(),
mem_slot_gaps: self.mem_slot_gaps.clone(),
routes: self.routes.clone(),
hv_cfg: self.hv_cfg,
})
}
fn check_capability(&self, c: VmCap) -> bool {
match c {
VmCap::DirtyLog => false,
// Strictly speaking, Gunyah supports pvclock, but Gunyah takes care
// of it and crosvm doesn't need to do anything for it
VmCap::PvClock => false,
VmCap::PvClockSuspend => false,
VmCap::Protected => true,
VmCap::EarlyInitCpuid => false,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
VmCap::BusLockDetect => false,
}
}
fn get_guest_phys_addr_bits(&self) -> u8 {
40
}
fn get_memory(&self) -> &GuestMemory {
&self.guest_mem
}
fn add_memory_region(
&mut self,
guest_addr: GuestAddress,
mem_region: Box<dyn MappedRegion>,
read_only: bool,
_log_dirty_pages: bool,
) -> Result<MemSlot> {
let pgsz = pagesize() as u64;
// Gunyah require to set the user memory region with page size aligned size. Safe to extend
// the mem.size() to be page size aligned because the mmap will round up the size to be
// page size aligned if it is not.
let size = (mem_region.size() as u64 + pgsz - 1) / pgsz * pgsz;
let end_addr = guest_addr.checked_add(size).ok_or(Error::new(EOVERFLOW))?;
if self.guest_mem.range_overlap(guest_addr, end_addr) {
return Err(Error::new(ENOSPC));
}
let mut regions = self.mem_regions.lock();
let mut gaps = self.mem_slot_gaps.lock();
let slot = match gaps.pop() {
Some(gap) => gap.0,
None => (regions.len() + self.guest_mem.num_regions() as usize) as MemSlot,
};
let res = unsafe {
set_user_memory_region(
&self.vm,
slot,
read_only,
guest_addr.offset() as u64,
size,
mem_region.as_ptr(),
)
};
if let Err(e) = res {
gaps.push(Reverse(slot));
return Err(e);
}
regions.insert(slot, (mem_region, guest_addr));
Ok(slot)
}
fn msync_memory_region(&mut self, slot: MemSlot, offset: usize, size: usize) -> Result<()> {
let mut regions = self.mem_regions.lock();
let (mem, _) = regions.get_mut(&slot).ok_or_else(|| Error::new(ENOENT))?;
mem.msync(offset, size).map_err(|err| match err {
MmapError::InvalidAddress => Error::new(EFAULT),
MmapError::NotPageAligned => Error::new(EINVAL),
MmapError::SystemCallFailed(e) => e,
_ => Error::new(EIO),
})
}
fn remove_memory_region(&mut self, _slot: MemSlot) -> Result<Box<dyn MappedRegion>> {
unimplemented!()
}
fn create_device(&self, _kind: DeviceKind) -> Result<SafeDescriptor> {
unimplemented!()
}
fn get_dirty_log(&self, _slot: MemSlot, _dirty_log: &mut [u8]) -> Result<()> {
unimplemented!()
}
fn register_ioevent(
&mut self,
evt: &Event,
addr: IoEventAddress,
datamatch: Datamatch,
) -> Result<()> {
let (do_datamatch, datamatch_value, datamatch_len) = match datamatch {
Datamatch::AnyLength => (false, 0, 0),
Datamatch::U8(v) => match v {
Some(u) => (true, u as u64, 1),
None => (false, 0, 1),
},
Datamatch::U16(v) => match v {
Some(u) => (true, u as u64, 2),
None => (false, 0, 2),
},
Datamatch::U32(v) => match v {
Some(u) => (true, u as u64, 4),
None => (false, 0, 4),
},
Datamatch::U64(v) => match v {
Some(u) => (true, u as u64, 8),
None => (false, 0, 8),
},
};
let mut flags = 0;
if do_datamatch {
flags |= 1 << GH_IOEVENTFD_DATAMATCH;
}
let maddr = if let IoEventAddress::Mmio(maddr) = addr {
maddr
} else {
todo!()
};
let gh_fn_ioeventfd_arg = gh_fn_ioeventfd_arg {
fd: evt.as_raw_descriptor() as i32,
datamatch: datamatch_value,
len: datamatch_len,
addr: maddr,
flags,
..Default::default()
};
let function_desc = gh_fn_desc {
type_: GH_FN_IOEVENTFD,
arg_size: size_of::<gh_fn_ioeventfd_arg>() as u32,
arg: &gh_fn_ioeventfd_arg as *const gh_fn_ioeventfd_arg as u64,
};
let ret = unsafe { ioctl_with_ref(self, GH_VM_ADD_FUNCTION(), &function_desc) };
if ret == 0 {
Ok(())
} else {
errno_result()
}
}
fn unregister_ioevent(
&mut self,
_evt: &Event,
_addr: IoEventAddress,
_datamatch: Datamatch,
) -> Result<()> {
unimplemented!()
}
fn handle_io_events(&self, _addr: IoEventAddress, _data: &[u8]) -> Result<()> {
Ok(())
}
fn get_pvclock(&self) -> Result<ClockState> {
unimplemented!()
}
fn set_pvclock(&self, _state: &ClockState) -> Result<()> {
unimplemented!()
}
fn add_fd_mapping(
&mut self,
slot: u32,
offset: usize,
size: usize,
fd: &dyn AsRawDescriptor,
fd_offset: u64,
prot: Protection,
) -> Result<()> {
let mut regions = self.mem_regions.lock();
let (region, _) = regions.get_mut(&slot).ok_or_else(|| Error::new(EINVAL))?;
match region.add_fd_mapping(offset, size, fd, fd_offset, prot) {
Ok(()) => Ok(()),
Err(MmapError::SystemCallFailed(e)) => Err(e),
Err(_) => Err(Error::new(EIO)),
}
}
fn remove_mapping(&mut self, slot: u32, offset: usize, size: usize) -> Result<()> {
let mut regions = self.mem_regions.lock();
let (region, _) = regions.get_mut(&slot).ok_or_else(|| Error::new(EINVAL))?;
match region.remove_mapping(offset, size) {
Ok(()) => Ok(()),
Err(MmapError::SystemCallFailed(e)) => Err(e),
Err(_) => Err(Error::new(EIO)),
}
}
fn handle_inflate(&mut self, _guest_address: GuestAddress, _size: u64) -> Result<()> {
unimplemented!()
}
fn handle_deflate(&mut self, _guest_address: GuestAddress, _size: u64) -> Result<()> {
unimplemented!()
}
}
const GH_RM_EXIT_TYPE_VM_EXIT: u16 = 0;
const GH_RM_EXIT_TYPE_PSCI_POWER_OFF: u16 = 1;
const GH_RM_EXIT_TYPE_PSCI_SYSTEM_RESET: u16 = 2;
const GH_RM_EXIT_TYPE_PSCI_SYSTEM_RESET2: u16 = 3;
const GH_RM_EXIT_TYPE_WDT_BITE: u16 = 4;
const GH_RM_EXIT_TYPE_HYP_ERROR: u16 = 5;
const GH_RM_EXIT_TYPE_ASYNC_EXT_ABORT: u16 = 6;
const GH_RM_EXIT_TYPE_VM_FORCE_STOPPED: u16 = 7;
pub struct GunyahVcpu {
vm: SafeDescriptor,
vcpu: SafeDescriptor,
id: usize,
run_mmap: MemoryMapping,
vcpu_run_handle_fingerprint: Arc<AtomicU64>,
}
pub(super) struct VcpuThread {
run: *mut gh_vcpu_run,
signal_num: Option<c_int>,
}
thread_local!(static VCPU_THREAD: RefCell<Option<VcpuThread>> = RefCell::new(None));
impl AsRawDescriptor for GunyahVcpu {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.vcpu.as_raw_descriptor()
}
}
impl Vcpu for GunyahVcpu {
fn try_clone(&self) -> Result<Self>
where
Self: Sized,
{
let vcpu = self.vcpu.try_clone()?;
let run_mmap = MemoryMappingBuilder::new(self.run_mmap.size())
.from_descriptor(&vcpu)
.build()
.map_err(|_| Error::new(ENOSPC))?;
Ok(GunyahVcpu {
vm: self.vm.try_clone()?,
vcpu,
id: self.id,
run_mmap,
vcpu_run_handle_fingerprint: self.vcpu_run_handle_fingerprint.clone(),
})
}
fn as_vcpu(&self) -> &dyn Vcpu {
self
}
#[allow(clippy::cast_ptr_alignment)]
fn take_run_handle(&self, signal_num: Option<c_int>) -> Result<VcpuRunHandle> {
fn vcpu_run_handle_drop() {
VCPU_THREAD.with(|v| {
// This assumes that a failure in `BlockedSignal::new` means the signal is already
// blocked and there it should not be unblocked on exit.
let _blocked_signal = &(*v.borrow())
.as_ref()
.and_then(|state| state.signal_num)
.map(BlockedSignal::new);
*v.borrow_mut() = None;
});
}
// Prevent `vcpu_run_handle_drop` from being called until we actually setup the signal
// blocking. The handle needs to be made now so that we can use the fingerprint.
let vcpu_run_handle = ManuallyDrop::new(VcpuRunHandle::new(vcpu_run_handle_drop));
// AcqRel ordering is sufficient to ensure only one thread gets to set its fingerprint to
// this Vcpu and subsequent `run` calls will see the fingerprint.
if self
.vcpu_run_handle_fingerprint
.compare_exchange(
0,
vcpu_run_handle.fingerprint().as_u64(),
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
)
.is_err()
{
return Err(Error::new(EBUSY));
}
// Block signal while we add -- if a signal fires (very unlikely,
// as this means something is trying to pause the vcpu before it has
// even started) it'll try to grab the read lock while this write
// lock is grabbed and cause a deadlock.
// Assuming that a failure to block means it's already blocked.
let _blocked_signal = signal_num.map(BlockedSignal::new);
VCPU_THREAD.with(|v| {
if v.borrow().is_none() {
*v.borrow_mut() = Some(VcpuThread {
run: self.run_mmap.as_ptr() as *mut gh_vcpu_run,
signal_num,
});
Ok(())
} else {
Err(Error::new(EBUSY))
}
})?;
Ok(ManuallyDrop::into_inner(vcpu_run_handle))
}
fn run(&mut self, run_handle: &VcpuRunHandle) -> Result<VcpuExit> {
if self
.vcpu_run_handle_fingerprint
.load(std::sync::atomic::Ordering::Acquire)
!= run_handle.fingerprint().as_u64()
{
panic!("invalid VcpuRunHandle used to run Vcpu");
}
// Safe because we know our file is a VCPU fd and we verify the return result.
let ret = unsafe { ioctl(self, GH_VCPU_RUN()) };
if ret != 0 {
return errno_result();
}
// Safe because we know we mapped enough memory to hold the gh_vcpu_run struct
// because the kernel told us how large it is.
let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut gh_vcpu_run) };
match run.exit_reason {
GH_VCPU_EXIT_MMIO => Ok(VcpuExit::Mmio),
GH_VCPU_EXIT_STATUS => {
let status = unsafe { &mut run.__bindgen_anon_1.status };
match status.status {
GH_VM_STATUS_GH_VM_STATUS_LOAD_FAILED => Ok(VcpuExit::FailEntry {
hardware_entry_failure_reason: 0,
}),
GH_VM_STATUS_GH_VM_STATUS_CRASHED => Ok(VcpuExit::SystemEventCrash),
GH_VM_STATUS_GH_VM_STATUS_EXITED => {
info!("exit type {}", status.exit_info.type_);
match status.exit_info.type_ {
GH_RM_EXIT_TYPE_VM_EXIT => Ok(VcpuExit::SystemEventShutdown),
GH_RM_EXIT_TYPE_PSCI_POWER_OFF => Ok(VcpuExit::SystemEventShutdown),
GH_RM_EXIT_TYPE_PSCI_SYSTEM_RESET => Ok(VcpuExit::SystemEventReset),
GH_RM_EXIT_TYPE_PSCI_SYSTEM_RESET2 => Ok(VcpuExit::SystemEventReset),
GH_RM_EXIT_TYPE_WDT_BITE => Ok(VcpuExit::SystemEventCrash),
GH_RM_EXIT_TYPE_HYP_ERROR => Ok(VcpuExit::SystemEventCrash),
GH_RM_EXIT_TYPE_ASYNC_EXT_ABORT => Ok(VcpuExit::SystemEventCrash),
GH_RM_EXIT_TYPE_VM_FORCE_STOPPED => Ok(VcpuExit::SystemEventShutdown),
r => {
warn!("Unknown exit type: {}", r);
Err(Error::new(EINVAL))
}
}
}
r => {
warn!("Unknown vm status: {}", r);
Err(Error::new(EINVAL))
}
}
}
r => {
warn!("unknown gh exit reason: {}", r);
Err(Error::new(EINVAL))
}
}
}
fn id(&self) -> usize {
self.id
}
fn set_immediate_exit(&self, exit: bool) {
// Safe because we know we mapped enough memory to hold the kvm_run struct because the
// kernel told us how large it was. The pointer is page aligned so casting to a different
// type is well defined, hence the clippy allow attribute.
let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut gh_vcpu_run) };
run.immediate_exit = exit.into();
}
fn set_local_immediate_exit(exit: bool)
where
Self: Sized,
{
VCPU_THREAD.with(|v| {
if let Some(state) = &(*v.borrow()) {
unsafe {
(*state.run).immediate_exit = exit.into();
};
}
});
}
fn set_local_immediate_exit_fn(&self) -> extern "C" fn() {
extern "C" fn f() {
GunyahVcpu::set_local_immediate_exit(true);
}
f
}
fn handle_mmio(&self, handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>) -> Result<()> {
// Safe because we know we mapped enough memory to hold the gh_vcpu_run struct because the
// kernel told us how large it was. The pointer is page aligned so casting to a different
// type is well defined
let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut gh_vcpu_run) };
// Verify that the handler is called in the right context.
assert!(run.exit_reason == GH_VCPU_EXIT_MMIO);
// Safe because the exit_reason (which comes from the kernel) told us which
// union field to use.
let mmio = unsafe { &mut run.__bindgen_anon_1.mmio };
let address = mmio.phys_addr;
let size = min(mmio.len as usize, mmio.data.len());
if mmio.is_write != 0 {
handle_fn(IoParams {
address,
size,
operation: IoOperation::Write { data: mmio.data },
});
Ok(())
} else if let Some(data) = handle_fn(IoParams {
address,
size,
operation: IoOperation::Read,
}) {
mmio.data[..size].copy_from_slice(&data[..size]);
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
fn handle_io(&self, _handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>) -> Result<()> {
unreachable!()
}
fn handle_hyperv_hypercall(&self, _func: &mut dyn FnMut(HypervHypercall) -> u64) -> Result<()> {
unreachable!()
}
fn handle_rdmsr(&self, _data: u64) -> Result<()> {
unreachable!()
}
fn handle_wrmsr(&self) {
unreachable!()
}
fn pvclock_ctrl(&self) -> Result<()> {
Err(Error::new(ENOTSUP))
}
fn set_signal_mask(&self, _signals: &[c_int]) -> Result<()> {
unimplemented!()
}
unsafe fn enable_raw_capability(&self, _cap: u32, _args: &[u64; 4]) -> Result<()> {
unimplemented!()
}
}

View file

@ -7,6 +7,12 @@
pub mod aarch64;
pub mod caps;
#[cfg(all(
unix,
any(target_arch = "arm", target_arch = "aarch64"),
feature = "gunyah"
))]
pub mod gunyah;
#[cfg(all(windows, feature = "haxm"))]
pub mod haxm;
#[cfg(unix)]