| // Copyright 2017 The Fuchsia Authors | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| //! Guest physical address space management (`<hypervisor/aspace.h>`). | |
| use core::mem::{align_of, size_of}; | |
| use core::ptr::with_exposed_provenance_mut; | |
| use fbl::RefPtr; | |
| use ksync::LockToken; | |
| use range_check::in_range_max; | |
| use zr::static_assert; | |
| use zx_status::Status; | |
| use zx_types::{zx_gpaddr_t, zx_vaddr_t}; | |
| use crate::kernel::types::{PAddr, VAddr}; | |
| use crate::vm::arch_vm_aspace::{ | |
| ARCH_MMU_FLAG_CACHED, ARCH_MMU_FLAG_PERM_EXECUTE, ARCH_MMU_FLAG_PERM_READ, | |
| ARCH_MMU_FLAG_PERM_WRITE, ARCH_MMU_FLAG_UNCACHED_DEVICE, ArchMmuFlags, ArchVmAspace, | |
| }; | |
| use crate::vm::fault::flag as pf_flag; | |
| use crate::vm::page_source::MultiPageRequest; | |
| use crate::vm::pinned_vm_object::PinnedVmObject; | |
| use crate::vm::vm_address_region::{VmAddressRegion, VmAddressRegionOpChildren, flag as vmar_flag}; | |
| use crate::vm::vm_aspace::{Type, VmAspace, VmAspaceLockClass}; | |
| use crate::vm::vm_mapping::VmMapping; | |
| use crate::vm::vm_object_physical::VmObjectPhysical; | |
| const INTERRUPT_MMU_FLAGS: ArchMmuFlags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; | |
| const GUEST_MMU_FLAGS: ArchMmuFlags = | |
| ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; | |
| /// RAII object that holds a mapping of guest physical address space to the host | |
| /// kernel virtual address space. Can be used to map a frequently accessed | |
| /// portion of guest physical memory for faster access. | |
| #[repr(C)] | |
| #[derive(Default)] | |
| pub struct GuestPtr { | |
| mapping: Option<RefPtr<VmMapping>>, | |
| pinned_vmo: PinnedVmObject, | |
| offset: zx_vaddr_t, | |
| } | |
| static_assert!(size_of::<GuestPtr>() == 40); | |
| static_assert!(align_of::<GuestPtr>() == 8); | |
| impl GuestPtr { | |
| /// Creates a new `GuestPtr` from an active host `mapping`, `pinned_vmo`, and intra-page | |
| /// `offset`. | |
| fn new(mapping: RefPtr<VmMapping>, pinned_vmo: PinnedVmObject, offset: zx_vaddr_t) -> Self { | |
| Self { mapping: Some(mapping), pinned_vmo, offset } | |
| } | |
| /// Destroys the host mapping and unpins the underlying VMO pages. | |
| pub fn reset(&mut self) { | |
| if let Some(mapping) = self.mapping.take() { | |
| let _ = mapping.destroy(); | |
| } | |
| self.pinned_vmo = PinnedVmObject::default(); | |
| } | |
| /// Returns a typed pointer to the mapped guest memory, or null if `size_of::<T>()` exceeds | |
| /// the remaining mapped size from `offset`. | |
| pub fn as_ptr<T>(&self) -> *mut T { | |
| let mapping = self.mapping.as_ref().expect("GuestPtr is not initialized"); | |
| if self.offset + size_of::<T>() > mapping.size() { | |
| return core::ptr::null_mut(); | |
| } | |
| with_exposed_provenance_mut(mapping.base().0 + self.offset) | |
| } | |
| } | |
| impl Drop for GuestPtr { | |
| fn drop(&mut self) { | |
| self.reset(); | |
| } | |
| } | |
| /// Manages the guest physical address space (`VmAspace::Type::GuestPhysical`) for a hypervisor | |
| /// guest. | |
| #[repr(C)] | |
| #[derive(Default)] | |
| pub struct GuestPhysicalAspace { | |
| physical_aspace: Option<RefPtr<VmAspace>>, | |
| } | |
| static_assert!(size_of::<GuestPhysicalAspace>() == size_of::<usize>()); | |
| static_assert!(align_of::<GuestPhysicalAspace>() == align_of::<usize>()); | |
| impl GuestPhysicalAspace { | |
| /// Creates a new `GuestPhysicalAspace`. | |
| pub fn create() -> Result<Self, Status> { | |
| let physical_aspace = | |
| VmAspace::create(Type::GuestPhysical, b"guest_physical").ok_or(Status::NO_MEMORY)?; | |
| Ok(Self { physical_aspace: Some(physical_aspace) }) | |
| } | |
| #[inline] | |
| fn physical_aspace(&self) -> &RefPtr<VmAspace> { | |
| self.physical_aspace.as_ref().expect("GuestPhysicalAspace is not initialized") | |
| } | |
| /// Returns the size of the guest physical address space in bytes. | |
| pub fn size(&self) -> usize { | |
| self.physical_aspace().size() | |
| } | |
| /// Returns a reference to the underlying `ArchVmAspace`. | |
| pub fn arch_aspace(&self) -> &ArchVmAspace { | |
| self.physical_aspace().arch_aspace() | |
| } | |
| /// Returns the root `VmAddressRegion` of the guest physical address space. | |
| pub fn root_vmar(&self) -> RefPtr<VmAddressRegion> { | |
| self.physical_aspace().root_vmar().expect("GuestPhysicalAspace has no root VMAR") | |
| } | |
| /// Returns true if `guest_paddr` is backed by a `VmMapping` in this guest physical address | |
| /// space. | |
| pub fn is_mapped(&self, guest_paddr: zx_gpaddr_t) -> bool { | |
| ksync::lock!(let guard = self.physical_aspace().lock().lock()); | |
| self.find_mapping(guard.token(), guest_paddr).is_some() | |
| } | |
| /// Maps the physical interrupt controller range `[host_paddr, host_paddr + len)` at | |
| /// `guest_paddr` in the guest physical address space. | |
| pub fn map_interrupt_controller( | |
| &mut self, | |
| guest_paddr: zx_gpaddr_t, | |
| host_paddr: PAddr, | |
| len: usize, | |
| ) -> Result<(), Status> { | |
| let vmo = VmObjectPhysical::create(host_paddr, len)?; | |
| // SAFETY: `vmo` was just created and has no mappings or children yet. | |
| unsafe { | |
| vmo.set_mapping_cache_policy(ARCH_MMU_FLAG_UNCACHED_DEVICE)?; | |
| } | |
| let vmo_size = vmo.size() as usize; | |
| // The root VMAR will maintain a reference to the VmMapping internally so | |
| // we don't need to maintain a long-lived reference to the mapping here. | |
| let mapping_result = self.root_vmar().create_vm_mapping( | |
| guest_paddr, | |
| vmo_size, | |
| /* align_pow2*/ 0, | |
| vmar_flag::SPECIFIC, | |
| VmObjectPhysical::into_vm_object(vmo), | |
| /* vmo_offset */ 0, | |
| INTERRUPT_MMU_FLAGS, | |
| b"guest_interrupt_vmo", | |
| )?; | |
| // Write mapping to page table. | |
| if let Err(status) = mapping_result.mapping.map_range(0, vmo_size, true, false) { | |
| let _ = mapping_result.mapping.destroy(); | |
| return Err(status); | |
| } | |
| Ok(()) | |
| } | |
| /// Unmaps `[guest_paddr, guest_paddr + len)` from the deepest containing VMAR in the guest | |
| /// physical address space. | |
| pub fn unmap_range(&mut self, guest_paddr: zx_gpaddr_t, len: usize) -> Result<(), Status> { | |
| // Walk from the root down through any child VMARs for this range so we can do an unmap. | |
| let mut vmar = self.root_vmar(); | |
| while let Some(next) = vmar.find_region(VAddr(guest_paddr)) { | |
| let Some(next_vmar) = next.as_vm_address_region() else { | |
| break; | |
| }; | |
| vmar = next_vmar; | |
| } | |
| // SAFETY: Unmaps a guest physical address range rather than active host kernel memory. | |
| unsafe { vmar.unmap(VAddr(guest_paddr), len, VmAddressRegionOpChildren::No) } | |
| } | |
| /// Resolves a guest physical page fault at `guest_paddr`. | |
| pub fn page_fault(&mut self, guest_paddr: zx_gpaddr_t) -> Result<(), Status> { | |
| pin_init::stack_pin_init!(let page_request = MultiPageRequest::new()); | |
| let guest_paddr = page::round_down(guest_paddr); | |
| loop { | |
| let status = { | |
| ksync::lock!(let mut guard = self.physical_aspace().lock().lock()); | |
| let mapping = | |
| self.find_mapping(guard.token(), guest_paddr).ok_or(Status::NOT_FOUND)?; | |
| // In order to avoid re-faulting if the guest changes how it accesses | |
| // guest physical memory, and to avoid the need for invalidation of the | |
| // guest physical address space on x86 (through the use of INVEPT), we | |
| // fault the page with the maximum allowable permissions of the mapping. | |
| let mmu_flags = mapping.arch_mmu_flags_locked(guard.token(), VAddr(guest_paddr)); | |
| let mut pf_flags = pf_flag::GUEST | pf_flag::HW_FAULT; | |
| if (mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) != 0 { | |
| pf_flags |= pf_flag::WRITE; | |
| } | |
| if (mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) != 0 { | |
| pf_flags |= pf_flag::INSTRUCTION; | |
| } | |
| // SAFETY: `mapping` is alive and the object lock is not held. `guest_paddr` is | |
| // page-aligned and within `mapping`, the additional page count is less than | |
| // `usize::MAX`, and `page_request` does not hold an active request. | |
| let (fault_status, _) = unsafe { | |
| mapping.page_fault_locked( | |
| guard.as_mut().token_mut(), | |
| VAddr(guest_paddr), | |
| pf_flags, | |
| 0, | |
| page_request.as_mut(), | |
| ) | |
| }; | |
| fault_status | |
| }; | |
| if status == Err(Status::SHOULD_WAIT) { | |
| page_request.as_mut().wait(true)?; | |
| } else { | |
| return status; | |
| } | |
| } | |
| } | |
| /// Creates a pinned host kernel mapping (`GuestPtr`) for `[guest_paddr, guest_paddr + len)`. | |
| /// | |
| /// Until the returned `GuestPtr` is reset or dropped, the range is mapped writable in the | |
| /// kernel address space and its pages are pinned, so a non-null pointer returned by | |
| /// `GuestPtr::as_ptr` is valid for reads and writes of the requested type. The memory is | |
| /// shared with the guest, so callers should access it through raw pointers rather than Rust | |
| /// references. | |
| pub fn create_guest_ptr( | |
| &self, | |
| guest_paddr: zx_gpaddr_t, | |
| len: usize, | |
| name: &[u8], | |
| ) -> Result<GuestPtr, Status> { | |
| let begin = page::round_down(guest_paddr); | |
| let end = guest_paddr.wrapping_add(len).wrapping_add(page::MASK) & !page::MASK; | |
| let mapping_len = end.wrapping_sub(begin); | |
| if begin > end || !in_range_max(begin as u64, mapping_len as u64, self.size() as u64) { | |
| return Err(Status::INVALID_ARGS); | |
| } | |
| let (intra_mapping_offset, mapping_object_offset, vmo) = { | |
| ksync::lock!(let guard = self.physical_aspace().lock().lock()); | |
| let guest_mapping = self.find_mapping(guard.token(), begin).ok_or(Status::NOT_FOUND)?; | |
| let intra_mapping_offset = (begin - guest_mapping.base().0) as u64; | |
| if !in_range_max(intra_mapping_offset, mapping_len as u64, guest_mapping.size() as u64) | |
| { | |
| // The address range is not contained within a single mapping. | |
| return Err(Status::OUT_OF_RANGE); | |
| } | |
| let mapping_object_offset = guest_mapping.object_offset(); | |
| let vmo = | |
| guest_mapping.vmo_locked(guard.token()).expect("live mapping must have a VMO"); | |
| (intra_mapping_offset, mapping_object_offset, vmo) | |
| }; | |
| // Pin the range of the guest VMO to ensure the user cannot manipulate it to cause our | |
| // kernel mapping to become invalidate and generate faults. | |
| let pinned_vmo = PinnedVmObject::create( | |
| vmo.clone(), | |
| mapping_object_offset + intra_mapping_offset, | |
| mapping_len as u64, | |
| true, | |
| )?; | |
| let kernel_vmar = | |
| VmAspace::kernel_aspace().root_vmar().expect("kernel aspace has no root VMAR"); | |
| let host_mapping_result = kernel_vmar.create_vm_mapping( | |
| /* mapping_offset */ 0, | |
| mapping_len, | |
| /* align_pow2 */ 0, | |
| /* vmar_flags */ 0, | |
| vmo, | |
| mapping_object_offset + intra_mapping_offset, | |
| GUEST_MMU_FLAGS, | |
| name, | |
| )?; | |
| // Pre-populate the page tables so there's no need for kernel page faults. | |
| host_mapping_result.mapping.map_range(0, mapping_len, true, false)?; | |
| Ok(GuestPtr::new(host_mapping_result.mapping, pinned_vmo, guest_paddr - begin)) | |
| } | |
| fn find_mapping( | |
| &self, | |
| token: &LockToken<'_, VmAspaceLockClass>, | |
| guest_paddr: zx_gpaddr_t, | |
| ) -> Option<RefPtr<VmMapping>> { | |
| let mut region = self.physical_aspace().root_vmar_locked(token)?; | |
| while let Some(next) = region.find_region_locked(token, VAddr(guest_paddr)) { | |
| if let Some(mapping) = next.as_vm_mapping() { | |
| return Some(mapping); | |
| } | |
| region = next.as_vm_address_region()?; | |
| } | |
| None | |
| } | |
| } | |
| impl Drop for GuestPhysicalAspace { | |
| fn drop(&mut self) { | |
| if let Some(physical_aspace) = self.physical_aspace.take() { | |
| // VmAspace maintains a circular reference with it's root VMAR. We need to | |
| // destroy the VmAspace in order to break that reference and allow the | |
| // VmAspace to be destructed. | |
| let _ = physical_aspace.destroy(); | |
| } | |
| } | |
| } | |
| // Note: `hypervisor_unittest.cc` exercises `GuestPhysicalAspace` on both `x86_64` and `aarch64` | |
| // via the C++ facade; `aspace_rust` is currently gated on `x86_64` because `aarch64` requires | |
| // `arm64_get_boot_el()`, `arm64_mmu_features.vhe`, and `ArchVmAspace::arch_set_asid`, which are | |
| // not yet exposed in Rust. | |
| /// Unit tests for `GuestPhysicalAspace` and `GuestPtr`. | |
| #[cfg(all(ktest, target_arch = "x86_64"))] | |
| #[unittest::suite(name = "aspace_rust")] | |
| mod tests { | |
| use crate::vm::pmm; | |
| use crate::vm::scanner::AutoVmScannerDisable; | |
| use crate::vm::vm_object_paged::VmObjectPaged; | |
| use unittest::{expect_eq, expect_err, expect_false, expect_ok, expect_true}; | |
| use zx_types::{ | |
| ZX_CACHE_POLICY_UNCACHED, ZX_CACHE_POLICY_UNCACHED_DEVICE, ZX_CACHE_POLICY_WRITE_COMBINING, | |
| }; | |
| const MMU_FLAGS: ArchMmuFlags = | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE; | |
| fn create_vmo(vmo_size: usize) -> Result<RefPtr<VmObjectPaged>, Status> { | |
| VmObjectPaged::create(pmm::ALLOC_FLAG_ANY, 0, vmo_size as u64) | |
| } | |
| fn commit_vmo(vmo: &VmObjectPaged) -> Result<(), Status> { | |
| vmo.commit_range(0, vmo.size()) | |
| } | |
| fn create_mapping( | |
| vmar: &VmAddressRegion, | |
| vmo: RefPtr<VmObjectPaged>, | |
| addr: zx_gpaddr_t, | |
| mmu_flags: ArchMmuFlags, | |
| ) -> Result<(), Status> { | |
| let vmo_size = vmo.size() as usize; | |
| vmar.create_vm_mapping( | |
| addr, | |
| vmo_size, | |
| 0, | |
| vmar_flag::SPECIFIC, | |
| VmObjectPaged::into_vm_object(vmo), | |
| 0, | |
| mmu_flags, | |
| b"vmo", | |
| ) | |
| .map(|_| ()) | |
| } | |
| fn create_sub_vmar( | |
| vmar: &VmAddressRegion, | |
| offset: usize, | |
| size: usize, | |
| ) -> Result<RefPtr<VmAddressRegion>, Status> { | |
| vmar.create_sub_vmar(offset, size, 0, vmar.flags() | vmar_flag::SPECIFIC, b"vmar") | |
| } | |
| /// Verifies that `unmap_range` unmaps a mapped page in the guest physical address space. | |
| #[test] | |
| fn guest_physical_aspace_unmap_range() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| // Unmap page. | |
| expect_ok!(gpa.unmap_range(0, page::SIZE)); | |
| // Verify `is_mapped` for unmapped address fails. | |
| expect_false!(gpa.is_mapped(0)); | |
| } | |
| /// Verifies that `unmap_range` succeeds when the range is outside any existing mapping. | |
| #[test] | |
| fn guest_physical_aspace_unmap_range_outside_of_mapping() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| // Unmap page. | |
| expect_ok!(gpa.unmap_range(page::SIZE * 8, page::SIZE)); | |
| } | |
| /// Verifies that `unmap_range` unmaps ranges spanning multiple mappings. | |
| #[test] | |
| fn guest_physical_aspace_unmap_range_multiple_mappings() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo1 = create_vmo(page::SIZE * 2).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo1, 0, MMU_FLAGS)); | |
| let vmo2 = create_vmo(page::SIZE * 2).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo2, page::SIZE * 3, MMU_FLAGS)); | |
| // Unmap pages. | |
| expect_ok!(gpa.unmap_range(page::SIZE, page::SIZE * 3)); | |
| // Verify `is_mapped` for unmapped addresses fails. | |
| for addr in (page::SIZE..page::SIZE * 4).step_by(page::SIZE) { | |
| expect_false!(gpa.is_mapped(addr)); | |
| } | |
| // Verify `is_mapped` for mapped addresses succeeds. | |
| expect_true!(gpa.is_mapped(0)); | |
| expect_true!(gpa.is_mapped(page::SIZE * 4)); | |
| } | |
| /// Verifies that `unmap_range` rejects ranges intersecting sub-VMARs. | |
| #[test] | |
| fn guest_physical_aspace_unmap_range_sub_region() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let root_vmar = gpa.root_vmar(); | |
| // To test partial unmapping within sub-VMAR: | |
| // Sub-VMAR from [0, page::SIZE * 2). | |
| // Map within sub-VMAR from [page::SIZE, page::SIZE * 2). | |
| let sub_vmar1 = | |
| create_sub_vmar(&root_vmar, 0, page::SIZE * 2).expect("Failed to create sub-VMAR"); | |
| expect_true!(sub_vmar1.has_parent()); | |
| let vmo1 = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&sub_vmar1, vmo1, page::SIZE, MMU_FLAGS)); | |
| // To test destroying of sub-VMAR: | |
| // Sub-VMAR from [page::SIZE * 2, page::SIZE * 3). | |
| // Map within sub-VMAR from [0, page::SIZE). | |
| let sub_vmar2 = create_sub_vmar(&root_vmar, page::SIZE * 2, page::SIZE) | |
| .expect("Failed to create sub-VMAR"); | |
| expect_true!(sub_vmar2.has_parent()); | |
| let vmo2 = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&sub_vmar2, vmo2, 0, MMU_FLAGS)); | |
| // To test partial unmapping within root-VMAR: | |
| // Map within root-VMAR from [page::SIZE * 3, page::SIZE * 5). | |
| let vmo3 = create_vmo(page::SIZE * 2).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&root_vmar, vmo3, page::SIZE * 3, MMU_FLAGS)); | |
| // Cannot unmap spanning across two different sub-vmars. | |
| expect_err!(gpa.unmap_range(page::SIZE, page::SIZE * 3), Status::INVALID_ARGS); | |
| // Verify `is_mapped` for mapped addresses succeeds. | |
| expect_true!(gpa.is_mapped(page::SIZE * 4)); | |
| // Verify that sub-VMARs still have a parent. | |
| expect_true!(sub_vmar1.has_parent()); | |
| expect_true!(sub_vmar2.has_parent()); | |
| } | |
| /// Verifies that multiple mappings of a single VMO at different offsets work properly. | |
| #[test] | |
| fn guest_physical_address_space_single_vmo_multiple_mappings() { | |
| let _scanner_disable = AutoVmScannerDisable::new(); | |
| // Setup. | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE * 4).expect("Failed to create VMO"); | |
| // Map a single page of this four page VMO at offset 0x1000 and offset 0x3000. | |
| expect_ok!( | |
| gpa.root_vmar() | |
| .create_vm_mapping( | |
| page::SIZE, | |
| page::SIZE, | |
| 0, | |
| vmar_flag::SPECIFIC, | |
| VmObjectPaged::into_vm_object(vmo.clone()), | |
| page::SIZE as u64, | |
| MMU_FLAGS, | |
| b"vmo", | |
| ) | |
| .map(|_| ()) | |
| ); | |
| expect_ok!( | |
| gpa.root_vmar() | |
| .create_vm_mapping( | |
| page::SIZE * 3, | |
| page::SIZE, | |
| 0, | |
| vmar_flag::SPECIFIC, | |
| VmObjectPaged::into_vm_object(vmo.clone()), | |
| (page::SIZE * 3) as u64, | |
| MMU_FLAGS, | |
| b"vmo", | |
| ) | |
| .map(|_| ()) | |
| ); | |
| expect_ok!(commit_vmo(&vmo)); | |
| // No mapping at 0x0 or 0x2000. | |
| expect_false!(gpa.is_mapped(0)); | |
| expect_false!(gpa.is_mapped(page::SIZE * 2)); | |
| // There is a mapping at 0x1000 and 0x3000. | |
| expect_true!(gpa.is_mapped(page::SIZE)); | |
| expect_true!(gpa.is_mapped(page::SIZE * 3)); | |
| } | |
| /// Verifies that `page_fault` faults in pages across mappings with different permission flags. | |
| #[test] | |
| fn guest_physical_aspace_page_fault() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo.clone(), 0, MMU_FLAGS)); | |
| expect_ok!(create_mapping( | |
| &gpa.root_vmar(), | |
| vmo.clone(), | |
| page::SIZE, | |
| ARCH_MMU_FLAG_PERM_READ | |
| )); | |
| expect_ok!(create_mapping( | |
| &gpa.root_vmar(), | |
| vmo.clone(), | |
| page::SIZE * 2, | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE, | |
| )); | |
| expect_ok!(create_mapping( | |
| &gpa.root_vmar(), | |
| vmo, | |
| page::SIZE * 3, | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE, | |
| )); | |
| // Fault in each page. | |
| for addr in (0..page::SIZE * 4).step_by(page::SIZE) { | |
| expect_ok!(gpa.page_fault(addr)); | |
| } | |
| } | |
| /// Verifies that `map_interrupt_controller` maps a physical page into the guest address space. | |
| #[test] | |
| fn guest_physical_aspace_map_interrupt_controller() { | |
| // Setup. | |
| let mut gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| // Allocate a page to use as the interrupt controller. | |
| let (vm_page, paddr) = pmm::alloc_page(0).expect("Unable to allocate a page"); | |
| // Map interrupt controller page in an arbitrary location. | |
| const GICV_ADDRESS: zx_gpaddr_t = 0x800001000; | |
| expect_ok!(gpa.map_interrupt_controller(GICV_ADDRESS, paddr, page::SIZE)); | |
| // Cleanup: must destroy `gpa` (which unmaps the physical page) before freeing `vm_page`. | |
| drop(gpa); | |
| // SAFETY: `vm_page` was allocated above and is no longer mapped. | |
| unsafe { | |
| pmm::free_page(vm_page); | |
| } | |
| } | |
| /// Verifies mapping a VMO with `ZX_CACHE_POLICY_UNCACHED` into a `GuestPhysicalAspace`. | |
| #[test] | |
| fn guest_physical_aspace_uncached() { | |
| // Setup. | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| // SAFETY: `vmo` was just created and has no mappings or children. | |
| expect_ok!(unsafe { | |
| vmo.set_mapping_cache_policy(ZX_CACHE_POLICY_UNCACHED as ArchMmuFlags) | |
| }); | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| } | |
| /// Verifies mapping a VMO with `ZX_CACHE_POLICY_UNCACHED_DEVICE` into a `GuestPhysicalAspace`. | |
| #[test] | |
| fn guest_physical_aspace_uncached_device() { | |
| // Setup. | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| // SAFETY: `vmo` was just created and has no mappings or children. | |
| expect_ok!(unsafe { | |
| vmo.set_mapping_cache_policy(ZX_CACHE_POLICY_UNCACHED_DEVICE as ArchMmuFlags) | |
| }); | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| } | |
| /// Verifies mapping a VMO with `ZX_CACHE_POLICY_WRITE_COMBINING` into a `GuestPhysicalAspace`. | |
| #[test] | |
| fn guest_physical_aspace_write_combining() { | |
| // Setup. | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| // SAFETY: `vmo` was just created and has no mappings or children. | |
| expect_ok!(unsafe { | |
| vmo.set_mapping_cache_policy(ZX_CACHE_POLICY_WRITE_COMBINING as ArchMmuFlags) | |
| }); | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| } | |
| /// Verifies `VmAddressRegion::protect` on a mapping in a `GuestPhysicalAspace`. | |
| #[test] | |
| fn guest_physical_aspace_protect() { | |
| // Setup. | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, 0, MMU_FLAGS)); | |
| // SAFETY: Changes permissions on a guest physical address range rather than active host | |
| // kernel memory. | |
| expect_ok!(unsafe { | |
| gpa.root_vmar().protect( | |
| VAddr(0), | |
| page::SIZE, | |
| ARCH_MMU_FLAG_PERM_WRITE, | |
| VmAddressRegionOpChildren::Yes, | |
| ) | |
| }); | |
| } | |
| /// Verifies `ArchVmAspace::query` across mappings with various permission flags. | |
| #[test] | |
| fn guest_physical_aspace_query() { | |
| // Disable the VM scanner to ensure pages stay committed into the VMO, and hence stay | |
| // mapped into the aspace before we query. | |
| let _scanner_disable = AutoVmScannerDisable::new(); | |
| // This test is arch independent so be conservative with the permission and assume that | |
| // read is needed for any other permission. | |
| const MMU_FLAG_TESTS: [ArchMmuFlags; 4] = [ | |
| ARCH_MMU_FLAG_PERM_READ, | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE, | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE, | |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE, | |
| ]; | |
| let vmo = create_vmo(page::SIZE).expect("Failed to create VMO"); | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| for flags in MMU_FLAG_TESTS { | |
| let mapping_result = gpa | |
| .root_vmar() | |
| .create_vm_mapping( | |
| 0, | |
| page::SIZE, | |
| 0, | |
| vmar_flag::SPECIFIC, | |
| VmObjectPaged::into_vm_object(vmo.clone()), | |
| 0, | |
| flags, | |
| b"vmo", | |
| ) | |
| .expect("Failed to create mapping"); | |
| expect_ok!(mapping_result.mapping.map_range(0, page::SIZE, true, false)); | |
| let (_, query_flags) = gpa.arch_aspace().query(0).expect("Failed to query"); | |
| expect_eq!(flags, query_flags); | |
| // Cleanup the mapping for next iteration. | |
| expect_ok!(mapping_result.mapping.destroy()); | |
| } | |
| } | |
| /// Verifies that `create_guest_ptr` maps and pins a guest region accessible from the host. | |
| #[test] | |
| fn guest_physical_aspace_create_guest_ptr() { | |
| let gpa = GuestPhysicalAspace::create().expect("Failed to create GuestPhysicalAspace"); | |
| let vmo = create_vmo(page::SIZE * 2).expect("Failed to create VMO"); | |
| expect_ok!(create_mapping(&gpa.root_vmar(), vmo, page::SIZE, MMU_FLAGS)); | |
| let mut guest_ptr = gpa | |
| .create_guest_ptr(page::SIZE + 64, size_of::<u64>(), b"test-guest-ptr") | |
| .expect("Failed to create GuestPtr"); | |
| let ptr = guest_ptr.as_ptr::<u64>(); | |
| expect_true!(!ptr.is_null()); | |
| // SAFETY: `ptr` points to a mapped, pinned `u64` at `page::SIZE + 64`. | |
| unsafe { | |
| *ptr = 0xdeadbeef_cafebabe; | |
| expect_eq!(0xdeadbeef_cafebabe, *ptr); | |
| } | |
| // Requesting a type larger than the mapped page must return null. | |
| expect_true!(guest_ptr.as_ptr::<[u8; page::SIZE]>().is_null()); | |
| guest_ptr.reset(); | |
| } | |
| } |