| // 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 |
| |
| #include <align.h> |
| |
| #include <fbl/alloc_checker.h> |
| #include <hypervisor/guest_physical_address_space.h> |
| #include <kernel/range_check.h> |
| #include <ktl/move.h> |
| #include <vm/fault.h> |
| #include <vm/page_source.h> |
| #include <vm/vm_object_physical.h> |
| |
| #include <ktl/enforce.h> |
| |
| namespace { |
| |
| constexpr uint kInterruptMmuFlags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; |
| constexpr uint kGuestMmuFlags = |
| ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; |
| |
| fbl::RefPtr<VmMapping> FindMapping(fbl::RefPtr<VmAddressRegion> region, zx_gpaddr_t guest_paddr) { |
| for (fbl::RefPtr<VmAddressRegionOrMapping> next; (next = region->FindRegion(guest_paddr)); |
| region = next->as_vm_address_region()) { |
| if (next->is_mapping()) { |
| return next->as_vm_mapping(); |
| } |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| namespace hypervisor { |
| |
| zx::status<GuestPhysicalAddressSpace> GuestPhysicalAddressSpace::Create( |
| #if ARCH_ARM64 |
| uint16_t vmid |
| #endif |
| ) { |
| auto guest_aspace = VmAspace::Create(VmAspace::Type::GuestPhys, "guest_aspace"); |
| if (!guest_aspace) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| #if ARCH_ARM64 |
| guest_aspace->arch_aspace().arch_set_asid(vmid); |
| #endif |
| GuestPhysicalAddressSpace gpas; |
| gpas.guest_aspace_ = ktl::move(guest_aspace); |
| return zx::ok(ktl::move(gpas)); |
| } |
| |
| GuestPhysicalAddressSpace::~GuestPhysicalAddressSpace() { |
| // 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. |
| if (guest_aspace_) { |
| guest_aspace_->Destroy(); |
| } |
| } |
| |
| bool GuestPhysicalAddressSpace::IsMapped(zx_gpaddr_t guest_paddr) const { |
| return FindMapping(RootVmar(), guest_paddr) != nullptr; |
| } |
| |
| zx::status<> GuestPhysicalAddressSpace::MapInterruptController(zx_gpaddr_t guest_paddr, |
| zx_paddr_t host_paddr, size_t len) { |
| fbl::RefPtr<VmObjectPhysical> vmo; |
| zx_status_t status = VmObjectPhysical::Create(host_paddr, len, &vmo); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| status = vmo->SetMappingCachePolicy(ARCH_MMU_FLAG_UNCACHED_DEVICE); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| // 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. |
| fbl::RefPtr<VmMapping> mapping; |
| status = RootVmar()->CreateVmMapping(guest_paddr, vmo->size(), /* align_pow2*/ 0, |
| VMAR_FLAG_SPECIFIC, vmo, /* vmo_offset */ 0, |
| kInterruptMmuFlags, "guest_interrupt_vmo", &mapping); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| // Write mapping to page table. |
| status = mapping->MapRange(0, vmo->size(), true); |
| if (status != ZX_OK) { |
| mapping->Destroy(); |
| return zx::error(status); |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::status<> GuestPhysicalAddressSpace::UnmapRange(zx_gpaddr_t guest_paddr, size_t len) { |
| zx_status_t status = RootVmar()->UnmapAllowPartial(guest_paddr, len); |
| return zx::make_status(status); |
| } |
| |
| zx::status<> GuestPhysicalAddressSpace::PageFault(zx_gpaddr_t guest_paddr) { |
| // TOOD(fxb/94078): Enforce no other locks are held here since we may wait on the page request. |
| __UNINITIALIZED LazyPageRequest page_request; |
| |
| zx_status_t status = ZX_OK; |
| do { |
| fbl::RefPtr<VmMapping> mapping = FindMapping(RootVmar(), guest_paddr); |
| if (!mapping) { |
| return zx::error(ZX_ERR_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. |
| { |
| Guard<Mutex> guard{mapping->lock()}; |
| uint pf_flags = VMM_PF_FLAG_GUEST | VMM_PF_FLAG_HW_FAULT; |
| uint mmu_flags = mapping->arch_mmu_flags_locked(guest_paddr); |
| if (mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) { |
| pf_flags |= VMM_PF_FLAG_WRITE; |
| } |
| if (mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) { |
| pf_flags |= VMM_PF_FLAG_INSTRUCTION; |
| } |
| |
| status = mapping->PageFault(guest_paddr, pf_flags, &page_request); |
| } |
| |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| zx_status_t st = page_request->Wait(); |
| if (st != ZX_OK) { |
| return zx::error(st); |
| } |
| } |
| } while (status == ZX_ERR_SHOULD_WAIT); |
| |
| return zx::make_status(status); |
| } |
| |
| zx::status<GuestPtr> GuestPhysicalAddressSpace::CreateGuestPtr(zx_gpaddr_t guest_paddr, size_t len, |
| const char* name) { |
| const zx_gpaddr_t begin = ROUNDDOWN(guest_paddr, PAGE_SIZE); |
| const zx_gpaddr_t end = ROUNDUP(guest_paddr + len, PAGE_SIZE); |
| const zx_gpaddr_t mapping_len = end - begin; |
| if (begin > end || !InRange(begin, mapping_len, size())) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| fbl::RefPtr<VmAddressRegionOrMapping> region = RootVmar()->FindRegion(begin); |
| if (!region) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| fbl::RefPtr<VmMapping> guest_mapping = region->as_vm_mapping(); |
| if (!guest_mapping) { |
| return zx::error(ZX_ERR_WRONG_TYPE); |
| } |
| const uint64_t intra_mapping_offset = begin - guest_mapping->base(); |
| if (!InRange(intra_mapping_offset, mapping_len, guest_mapping->size())) { |
| // The address range is not contained within a single mapping. |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| uint64_t mapping_object_offset; |
| { |
| Guard<Mutex> guard{guest_mapping->lock()}; |
| mapping_object_offset = guest_mapping->object_offset_locked(); |
| } |
| |
| fbl::RefPtr<VmMapping> host_mapping; |
| zx_status_t status = VmAspace::kernel_aspace()->RootVmar()->CreateVmMapping( |
| /* mapping_offset */ 0, mapping_len, |
| /* align_pow2 */ false, |
| /* vmar_flags */ 0, guest_mapping->vmo(), mapping_object_offset + intra_mapping_offset, |
| kGuestMmuFlags, name, &host_mapping); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| // Pre-populate the page tables so there's no need for kernel page faults. |
| status = host_mapping->MapRange(0, mapping_len, true); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| return zx::ok(GuestPtr(ktl::move(host_mapping), guest_paddr - begin)); |
| } |
| |
| } // namespace hypervisor |