Merge changes I2e8a8f64,I72769fab into main * changes: crosvm: gunyah: Support launching QTVMs hypervisor: gunyah: Add balloon support
diff --git a/hypervisor/src/gunyah/aarch64.rs b/hypervisor/src/gunyah/aarch64.rs index b1f3e70..3d08c53 100644 --- a/hypervisor/src/gunyah/aarch64.rs +++ b/hypervisor/src/gunyah/aarch64.rs
@@ -130,21 +130,13 @@ bell_node.set_prop("interrupts", &interrupts)?; } - let mut base_set = false; for region in self.guest_mem.regions() { let create_shm_node = match region.options.purpose { MemoryRegionPurpose::Bios => false, - 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 - } + MemoryRegionPurpose::GuestMemoryRegion => false, // Described by the "firmware-address" property MemoryRegionPurpose::ProtectedFirmwareRegion => false, - MemoryRegionPurpose::ReservedMemory => true, + MemoryRegionPurpose::ReservedMemory => false, MemoryRegionPurpose::StaticSwiotlbRegion => true, }; @@ -166,7 +158,6 @@ fdt_address: GuestAddress, fdt_size: usize, ) -> Result<()> { - // Gunyah sets the PC to the payload entry point, except for protected VMs. // The payload entry is the memory address where the kernel starts. // This memory region contains both the DTB and the kernel image, // so ensure they are located together. @@ -184,8 +175,32 @@ panic!("DTB and payload are not part of same memory region."); } + if self.vm_id.is_some() && self.pas_id.is_some() { + // Gunyah will find the metadata about the Qualcomm Trusted VM in the + // first few pages (decided at build time) of the primary payload region. + // This metadata consists of the elf header which tells Gunyah where + // the different elf segments (kernel/DTB/ramdisk) are. As we send the entire + // primary payload as a single memory parcel to Gunyah, with the offsets from + // the elf header, Gunyah can find the VM DTBOs. + // Pass on the primary payload region start address and its size for Qualcomm + // Trusted VMs. + for region in self.guest_mem.regions() { + if region.guest_addr.offset() == payload_entry_address.offset() { + self.set_vm_auth_type_to_qcom_trusted_vm(payload_entry_address, region.size.try_into().unwrap()); + break; + } + } + } + self.set_dtb_config(fdt_address, fdt_size)?; + // Gunyah sets the PC to the payload entry point for protected VMs without firmware. + // It needs to be 0 as Gunyah assumes it to be kernel start. + if self.hv_cfg.protection_type.isolates_memory() && + !self.hv_cfg.protection_type.runs_firmware() && payload_offset != 0 { + panic!("Payload offset must be zero"); + } + if let Err(e) = self.set_boot_pc(payload_entry_address.offset()) { if e.errno() == ENOTTY { // GH_VM_SET_BOOT_CONTEXT ioctl is not supported, but returning success
diff --git a/hypervisor/src/gunyah/gunyah_sys.rs b/hypervisor/src/gunyah/gunyah_sys.rs index 1097218..c52e567 100644 --- a/hypervisor/src/gunyah/gunyah_sys.rs +++ b/hypervisor/src/gunyah/gunyah_sys.rs
@@ -53,3 +53,21 @@ 0x12, gh_vm_firmware_config ); +ioctl_iow_nr!( + GH_VM_RECLAIM_REGION, + GH_ANDROID_IOCTL_TYPE, + 0x13, + gunyah_address_range +); +ioctl_iow_nr!( + GH_VM_ANDROID_MAP_CMA_MEM, + GH_ANDROID_IOCTL_TYPE, + 0x15, + gunyah_map_cma_mem_args +); +ioctl_iow_nr!( + GH_VM_ANDROID_SET_AUTH_TYPE, + GH_ANDROID_IOCTL_TYPE, + 0x16, + gunyah_auth_desc +);
diff --git a/hypervisor/src/gunyah/gunyah_sys/bindings.rs b/hypervisor/src/gunyah/gunyah_sys/bindings.rs index 325c431..19e378d 100644 --- a/hypervisor/src/gunyah/gunyah_sys/bindings.rs +++ b/hypervisor/src/gunyah/gunyah_sys/bindings.rs
@@ -24,6 +24,16 @@ pub const GH_VCPU_EXIT_STATUS: u32 = 2; pub const GH_ANDROID_IOCTL_TYPE: u8 = 65u8; pub const GH_VM_BOOT_CONTEXT_REG_SHIFT: u32 = 8; +pub type i8 = :: std :: os :: raw :: c_schar; +pub type u8 = :: std :: os :: raw :: c_uchar; +pub type i16 = :: std :: os :: raw :: c_short; +pub type u16 = :: std :: os :: raw :: c_ushort; +pub type i32 = :: std :: os :: raw :: c_int; +pub type u32 = :: std :: os :: raw :: c_uint; +pub type i64 = :: std :: os :: raw :: c_longlong; +pub type u64 = :: std :: os :: raw :: c_ulonglong; +pub type __s128 = i128; +pub type __u128 = u128; pub type __le16 = u16; pub type __be16 = u16; pub type __le32 = u32; @@ -32,7 +42,25 @@ pub type __be64 = u64; pub type __sum16 = u16; pub type __wsum = u32; -pub type __poll_t = ::std::os::raw::c_uint; +pub type __poll_t = :: std :: os :: raw :: c_uint; +pub const gunyah_auth_type_GUNYAH_ANDROID_PVM_TYPE : gunyah_auth_type = 0; +pub const gunyah_auth_type_GUNYAH_QCOM_TRUSTED_VM_TYPE : gunyah_auth_type = 1; +pub type gunyah_auth_type = :: std :: os :: raw :: c_uint; +#[repr (C)] +#[derive (Debug , Default , Copy , Clone)] +pub struct gunyah_qtvm_auth_arg { + pub vm_id : u16, + pub pas_id : u32, + pub guest_phys_addr : u64, + pub size : u64, +} +#[repr (C)] +#[derive (Debug , Default , Copy , Clone)] +pub struct gunyah_auth_desc { + pub type_ : u32, + pub arg_size : u32, + pub arg : u64, +} #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct gh_userspace_memory_region { @@ -164,3 +192,28 @@ pub guest_phys_addr: u64, pub size: u64, } +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct gunyah_address_range { + pub guest_phys_addr: u64, + pub size: u64, +} +pub const GUNYAH_MEM_ALLOW_READ : gunyah_map_flags = 1 ; +pub const GUNYAH_MEM_ALLOW_WRITE : gunyah_map_flags = 2 ; +pub const GUNYAH_MEM_ALLOW_EXEC : gunyah_map_flags = 4 ; +pub const GUNYAH_MEM_ALLOW_RWX : gunyah_map_flags = 7 ; +pub const GUNYAH_MEM_DEFAULT_ACCESS : gunyah_map_flags = 0 ; +pub const GUNYAH_MEM_FORCE_LEND : gunyah_map_flags = 16 ; +pub const GUNYAH_MEM_FORCE_SHARE : gunyah_map_flags = 32 ; +pub const GUNYAH_MEM_UNMAP : gunyah_map_flags = 256 ; +pub type gunyah_map_flags = :: std :: os :: raw :: c_uint ; +#[repr (C)] +#[derive (Debug , Default , Copy , Clone)] +pub struct gunyah_map_cma_mem_args { + pub label: u32, + pub guest_addr : u64, + pub flags : u32, + pub guest_mem_fd : u32, + pub offset : u64, + pub size : u64, +}
diff --git a/hypervisor/src/gunyah/mod.rs b/hypervisor/src/gunyah/mod.rs index 472dd77..2ab6c39 100644 --- a/hypervisor/src/gunyah/mod.rs +++ b/hypervisor/src/gunyah/mod.rs
@@ -19,6 +19,7 @@ use std::path::PathBuf; use std::sync::Arc; +use anyhow::Context; use base::errno_result; use base::error; use base::info; @@ -171,6 +172,44 @@ } } +fn map_cma_region( + vm: &SafeDescriptor, + slot: MemSlot, + lend: bool, + read_only: bool, + guest_addr: u64, + guest_mem_fd: u32, + size: u64, + offset: u64, +) -> Result<()> { + let mut flags = 0; + flags |= GUNYAH_MEM_ALLOW_READ | GUNYAH_MEM_ALLOW_EXEC; + if !read_only { + flags |= GUNYAH_MEM_ALLOW_WRITE; + } + if lend { + flags |= GUNYAH_MEM_FORCE_LEND; + } + else { + flags |= GUNYAH_MEM_FORCE_SHARE; + } + let region = gunyah_map_cma_mem_args { + label: slot, + guest_addr, + flags, + guest_mem_fd, + offset, + size, + }; + // SAFETY: safe because the return value is checked. + let ret = unsafe { ioctl_with_ref(vm, GH_VM_ANDROID_MAP_CMA_MEM, ®ion) }; + if ret == 0 { + Ok(()) + } else { + errno_result() + } +} + #[derive(PartialEq, Eq, Hash)] pub struct GunyahIrqRoute { irq: u32, @@ -180,6 +219,8 @@ pub struct GunyahVm { gh: Gunyah, vm: SafeDescriptor, + vm_id: Option<u16>, + pas_id: Option<u32>, 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 @@ -195,7 +236,7 @@ } impl GunyahVm { - pub fn new(gh: &Gunyah, guest_mem: GuestMemory, cfg: Config) -> Result<GunyahVm> { + pub fn new(gh: &Gunyah, vm_id: Option<u16>, pas_id: Option<u32>, guest_mem: GuestMemory, cfg: Config) -> Result<GunyahVm> { // SAFETY: // Safe because we know gunyah is a real gunyah fd as this module is the only one that can // make Gunyah objects. @@ -221,7 +262,18 @@ } else { false }; - if lend { + if region.options.file_backed.is_some() { + map_cma_region( + &vm_descriptor, + region.index as MemSlot, + lend, + !region.options.file_backed.unwrap().writable, + region.guest_addr.offset(), + region.shm.as_raw_descriptor().try_into().unwrap(), + region.size.try_into().unwrap(), + region.shm_offset, + )?; + } else if lend { // SAFETY: // Safe because the guest regions are guarnteed not to overlap. unsafe { @@ -253,6 +305,8 @@ Ok(GunyahVm { gh: gh.try_clone()?, vm: vm_descriptor, + vm_id, + pas_id, guest_mem, mem_regions: Arc::new(Mutex::new(BTreeMap::new())), mem_slot_gaps: Arc::new(Mutex::new(BinaryHeap::new())), @@ -261,6 +315,28 @@ }) } + pub fn set_vm_auth_type_to_qcom_trusted_vm(&self, payload_start: GuestAddress, payload_size: u64) -> Result<()> { + let gunyah_qtvm_auth_arg = gunyah_qtvm_auth_arg { + vm_id: self.vm_id.expect("VM ID not specified for a QTVM"), + pas_id: self.pas_id.expect("PAS ID not specified for a QTVM"), + // QTVMs have the metadata needed for authentication at the start of the guest addrspace. + guest_phys_addr: payload_start.offset(), + size: payload_size, + }; + let gunyah_auth_desc = gunyah_auth_desc { + type_: gunyah_auth_type_GUNYAH_QCOM_TRUSTED_VM_TYPE, + arg_size: size_of::<gunyah_qtvm_auth_arg>() as u32, + arg: &gunyah_qtvm_auth_arg as *const gunyah_qtvm_auth_arg as u64, + }; + // SAFETY: safe because the return value is checked. + let ret = unsafe { ioctl_with_ref(self, GH_VM_ANDROID_SET_AUTH_TYPE, &gunyah_auth_desc) }; + if ret == 0 { + Ok(()) + } else { + errno_result() + } + } + fn create_vcpu(&self, id: usize) -> Result<GunyahVcpu> { let gh_fn_vcpu_arg = gh_fn_vcpu_arg { id: id.try_into().unwrap(), @@ -363,6 +439,8 @@ Ok(GunyahVm { gh: self.gh.try_clone()?, vm: self.vm.try_clone()?, + vm_id: self.vm_id, + pas_id: self.pas_id, guest_mem: self.guest_mem.clone(), mem_regions: self.mem_regions.clone(), mem_slot_gaps: self.mem_slot_gaps.clone(), @@ -440,6 +518,26 @@ errno_result() } } + + fn handle_inflate(&self, guest_addr: GuestAddress, size: u64) -> Result<()> { + let range = gunyah_address_range { + guest_phys_addr: guest_addr.0, + size, + }; + + // SAFETY: Safe because we know this is a Gunyah VM + let ret = unsafe { ioctl_with_ref(self, GH_VM_RECLAIM_REGION, &range) }; + if ret != 0 { + warn!("Gunyah failed to reclaim {:?}", range); + return errno_result(); + } + + match self.guest_mem.remove_range(guest_addr, size) { + Ok(_) => Ok(()), + Err(vm_memory::Error::MemoryAccess(_, MmapError::SystemCallFailed(e))) => Err(e), + Err(_) => Err(Error::new(EIO)), + } + } } impl Vm for GunyahVm { @@ -450,6 +548,8 @@ Ok(GunyahVm { gh: self.gh.try_clone()?, vm: self.vm.try_clone()?, + vm_id: self.vm_id, + pas_id: self.pas_id, guest_mem: self.guest_mem.clone(), mem_regions: self.mem_regions.clone(), mem_slot_gaps: self.mem_slot_gaps.clone(), @@ -715,8 +815,12 @@ } } - fn handle_balloon_event(&mut self, _event: BalloonEvent) -> Result<()> { - unimplemented!() + fn handle_balloon_event(&mut self, event: BalloonEvent) -> Result<()> { + match event { + BalloonEvent::Inflate(m) => self.handle_inflate(m.guest_address, m.size), + BalloonEvent::Deflate(m) => Ok(()), + BalloonEvent::BalloonTargetReached(_) => Ok(()), + } } }
diff --git a/src/crosvm/sys/linux.rs b/src/crosvm/sys/linux.rs index 737a6a6..50e3b05 100644 --- a/src/crosvm/sys/linux.rs +++ b/src/crosvm/sys/linux.rs
@@ -1859,6 +1859,8 @@ #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))] fn run_gunyah( device_path: Option<&Path>, + qcom_trusted_vm_id: Option<u16>, + qcom_trusted_vm_pas_id: Option<u32>, cfg: Config, components: VmComponents, ) -> Result<ExitState> { @@ -1885,7 +1887,7 @@ None }; - let vm = GunyahVm::new(&gunyah, guest_mem, components.hv_cfg).context("failed to create vm")?; + let vm = GunyahVm::new(&gunyah, qcom_trusted_vm_id, qcom_trusted_vm_pas_id, guest_mem, components.hv_cfg).context("failed to create vm")?; // Check that the VM was actually created in protected mode as expected. if cfg.protection_type.isolates_memory() && !vm.check_capability(VmCap::Protected) { @@ -1936,6 +1938,8 @@ if gunyah_path.exists() { return Some(HypervisorKind::Gunyah { device: Some(gunyah_path.to_path_buf()), + qcom_trusted_vm_id: None, + qcom_trusted_vm_pas_id: None, }); } } @@ -1964,7 +1968,14 @@ any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah" ))] - HypervisorKind::Gunyah { device } => run_gunyah(device.as_deref(), cfg, components), + HypervisorKind::Gunyah { device, + qcom_trusted_vm_id, + qcom_trusted_vm_pas_id + } => run_gunyah( + device.as_deref(), + qcom_trusted_vm_id, + qcom_trusted_vm_pas_id, + cfg, components), } }
diff --git a/src/crosvm/sys/linux/config.rs b/src/crosvm/sys/linux/config.rs index 597156d..172a554 100644 --- a/src/crosvm/sys/linux/config.rs +++ b/src/crosvm/sys/linux/config.rs
@@ -34,6 +34,8 @@ #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))] Gunyah { device: Option<PathBuf>, + qcom_trusted_vm_id: Option<u16>, + qcom_trusted_vm_pas_id: Option<u32>, }, } @@ -617,7 +619,11 @@ assert_eq!( config.hypervisor, - Some(HypervisorKind::Gunyah { device: None }) + Some(HypervisorKind::Gunyah { + device: None, + qcom_trusted_vm_id: None, + qcom_trusted_vm_pas_id: None, + }) ); } @@ -635,7 +641,30 @@ assert_eq!( config.hypervisor, Some(HypervisorKind::Gunyah { - device: Some(PathBuf::from("/not/default")) + device: Some(PathBuf::from("/not/default")), + qcom_trusted_vm_id: None, + qcom_trusted_vm_pas_id: None, + }) + ); + } + + #[test] + #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))] + fn hypervisor_gunyah_device_with_qtvm_ids() { + let config: Config = crate::crosvm::cmdline::RunCommand::from_args( + &[], + &["--hypervisor", "gunyah[device=/not/default,qcom_trusted_vm_id=0,qcom_trusted_vm_pas_id=0]", "/dev/null"], + ) + .unwrap() + .try_into() + .unwrap(); + + assert_eq!( + config.hypervisor, + Some(HypervisorKind::Gunyah { + device: Some(PathBuf::from("/not/default")), + qcom_trusted_vm_id: Some(0), + qcom_trusted_vm_pas_id: Some(0), }) ); }