blob: 7c819c08b3861e2f9656b0401de416483e181961 [file] [log] [blame]
// 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 <hypervisor/guest_physical_address_space.h>
#include <arch/mmu.h>
#include <vm/arch_vm_aspace.h>
#include <vm/fault.h>
#include <vm/vm_object_physical.h>
#include <fbl/alloc_checker.h>
static const uint kPfFlags = VMM_PF_FLAG_WRITE | VMM_PF_FLAG_SW_FAULT;
static const uint kMmuFlags =
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE;
#if ARCH_X86_64
static const uint kApicMmuFlags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE;
#endif // ARCH_X86_64
namespace {
// Locate a VMO for a given vaddr.
struct AspaceVmoLocator final : public VmEnumerator {
AspaceVmoLocator(vaddr_t vaddr_) : vaddr(vaddr_) {}
bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, uint depth) final {
if (vaddr < map->base() || vaddr >= (map->base() + map->size())) {
// Mapping does not cover 'vaddr', return true to keep going.
return true;
}
vmo = map->vmo();
base = map->base();
return false;
}
fbl::RefPtr<VmObject> vmo = nullptr;
vaddr_t base = 0;
const vaddr_t vaddr;
};
} // namespace
zx_status_t GuestPhysicalAddressSpace::Create(fbl::RefPtr<VmObject> guest_phys_mem,
fbl::unique_ptr<GuestPhysicalAddressSpace>* _gpas) {
fbl::AllocChecker ac;
fbl::unique_ptr<GuestPhysicalAddressSpace> gpas(new (&ac)
GuestPhysicalAddressSpace(guest_phys_mem));
if (!ac.check())
return ZX_ERR_NO_MEMORY;
gpas->paspace_ = VmAspace::Create(VmAspace::TYPE_GUEST_PHYS, "guest_paspace");
if (!gpas->paspace_)
return ZX_ERR_NO_MEMORY;
// Initialize our VMAR with the provided VMO, mapped at address 0.
fbl::RefPtr<VmMapping> mapping;
zx_status_t result = gpas->paspace_->RootVmar()->CreateVmMapping(
0 /* mapping_offset */, guest_phys_mem->size(), /* align_pow2*/ 0, VMAR_FLAG_SPECIFIC,
guest_phys_mem, /* vmo_offset */ 0, kMmuFlags, "guest_phys_mem_vmo", &mapping);
if (result != ZX_OK)
return result;
*_gpas = fbl::move(gpas);
return ZX_OK;
}
GuestPhysicalAddressSpace::GuestPhysicalAddressSpace(fbl::RefPtr<VmObject> guest_phys_mem)
: guest_phys_mem_(guest_phys_mem) {}
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 (paspace_)
paspace_->Destroy();
}
#if ARCH_X86_64
zx_status_t GuestPhysicalAddressSpace::MapApicPage(vaddr_t guest_paddr, paddr_t host_paddr) {
fbl::RefPtr<VmObject> vmo;
zx_status_t result = VmObjectPhysical::Create(host_paddr, PAGE_SIZE, &vmo);
if (result != ZX_OK)
return result;
result = vmo->SetMappingCachePolicy(ARCH_MMU_FLAG_CACHED);
if (result != ZX_OK)
return result;
// 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;
result = paspace_->RootVmar()->CreateVmMapping(guest_paddr, vmo->size(), /* align_pow2*/ 0,
VMAR_FLAG_SPECIFIC, vmo, /* vmo_offset */ 0,
kApicMmuFlags, "guest_apic_vmo", &mapping);
if (result != ZX_OK)
return result;
// Write mapping to page table.
result = mapping->MapRange(0, vmo->size(), true);
if (result != ZX_OK) {
mapping->Destroy();
return result;
}
return ZX_OK;
}
#endif // ARCH_X86_64
zx_status_t GuestPhysicalAddressSpace::UnmapRange(vaddr_t guest_paddr, size_t size) {
return paspace_->RootVmar()->Unmap(guest_paddr, size);
}
zx_status_t GuestPhysicalAddressSpace::GetPage(vaddr_t guest_paddr, paddr_t* host_paddr) {
// Locate the VMO for the guest physical address (if present).
AspaceVmoLocator vmo_locator(guest_paddr);
paspace_->EnumerateChildren(&vmo_locator);
fbl::RefPtr<VmObject> vmo = vmo_locator.vmo;
if (!vmo)
return ZX_ERR_NOT_FOUND;
// Lookup the physical address of this page in the VMO.
vaddr_t offset = guest_paddr - vmo_locator.base;
return vmo->Lookup(offset, PAGE_SIZE, kPfFlags, guest_lookup_page, host_paddr);
}