blob: b5f2d6f44e0bf71bd51d403f14cda96b9d5215bb [file] [log] [blame]
// Copyright 2018 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 "second_level_pt.h"
#include <arch/x86/mmu.h>
#include "device_context.h"
#include "iommu_impl.h"
#define LOCAL_TRACE 0
namespace {
constexpr PtFlags kSlptRead = 1u << 0;
constexpr PtFlags kSlptWrite = 1u << 1;
constexpr PtFlags kSlptExecute = 1u << 2;
vaddr_t compute_vaddr_mask(PageTableLevel top_level) {
uint width;
switch (top_level) {
case PageTableLevel::PD_L:
width = 30;
break;
case PageTableLevel::PDP_L:
width = 39;
break;
case PageTableLevel::PML4_L:
width = 48;
break;
default:
panic("Unsupported iommu width\n");
}
// Valid vaddrs for mapping should be page-aligned and not larger than the
// width of the top level.
return ((1ull << width) - 1) & ~(PAGE_SIZE - 1);
}
} // namespace
namespace intel_iommu {
SecondLevelPageTable::SecondLevelPageTable(IommuImpl* iommu, DeviceContext* parent)
: iommu_(iommu),
parent_(parent),
needs_flushes_(!iommu->extended_caps()->page_walk_coherency()),
supports_2mb_(iommu->caps()->supports_second_level_2mb_page()),
supports_1gb_(iommu->caps()->supports_second_level_1gb_page()),
initialized_(false) {}
SecondLevelPageTable::~SecondLevelPageTable() { DEBUG_ASSERT(!initialized_); }
zx_status_t SecondLevelPageTable::Init(PageTableLevel top_level) {
DEBUG_ASSERT(!initialized_);
top_level_ = top_level;
valid_vaddr_mask_ = compute_vaddr_mask(top_level);
zx_status_t status = X86PageTableBase::Init(nullptr);
if (status != ZX_OK) {
return status;
}
initialized_ = true;
return ZX_OK;
}
void SecondLevelPageTable::Destroy() {
if (!initialized_) {
return;
}
size_t size = valid_vaddr_mask_ + PAGE_SIZE;
initialized_ = false;
X86PageTableBase::Destroy(0, size);
}
bool SecondLevelPageTable::allowed_flags(uint flags) {
constexpr uint kSupportedFlags =
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE;
if (flags & ~kSupportedFlags) {
return false;
}
return true;
}
// Validation for host physical addresses
bool SecondLevelPageTable::check_paddr(paddr_t paddr) { return x86_mmu_check_paddr(paddr); }
// Validation for virtual physical addresses
bool SecondLevelPageTable::check_vaddr(vaddr_t vaddr) { return !(vaddr & ~valid_vaddr_mask_); }
bool SecondLevelPageTable::supports_page_size(PageTableLevel level) {
switch (level) {
case PageTableLevel::PT_L:
return true;
case PageTableLevel::PD_L:
return supports_2mb_;
case PageTableLevel::PDP_L:
return supports_1gb_;
default:
return false;
}
}
IntermediatePtFlags SecondLevelPageTable::intermediate_flags() {
return kSlptRead | kSlptWrite | kSlptExecute;
}
PtFlags SecondLevelPageTable::terminal_flags(PageTableLevel level, uint flags) {
PtFlags terminal_flags = 0;
if (flags & ARCH_MMU_FLAG_PERM_READ) {
terminal_flags |= kSlptRead;
}
if (flags & ARCH_MMU_FLAG_PERM_WRITE) {
terminal_flags |= kSlptWrite;
}
if (flags & ARCH_MMU_FLAG_PERM_EXECUTE) {
terminal_flags |= kSlptExecute;
}
return terminal_flags;
}
PtFlags SecondLevelPageTable::split_flags(PageTableLevel level, PtFlags flags) {
// We don't need to relocate any flags on split
return flags;
}
// We disable thread safety analysis here, since the lock being held is being
// held across the MMU operations, but goes through code that is not aware of
// the lock.
void SecondLevelPageTable::TlbInvalidate(PendingTlbInvalidation* pending)
TA_NO_THREAD_SAFETY_ANALYSIS {
DEBUG_ASSERT(!pending->contains_global);
if (pending->full_shootdown) {
iommu_->InvalidateIotlbDomainAllLocked(parent_->domain_id());
pending->clear();
return;
}
constexpr uint kBitsPerLevel = 9;
for (uint i = 0; i < pending->count; ++i) {
const auto& item = pending->item[i];
uint address_mask = kBitsPerLevel * static_cast<uint>(item.page_level());
if (!item.is_terminal()) {
// If this is non-terminal, force the paging-structure cache to be
// cleared for this address still, even though a terminal mapping hasn't
// been changed.
// TODO(teisenbe): Not completely sure this is necessary. Including for
// now out of caution.
address_mask = 0;
}
iommu_->InvalidateIotlbPageLocked(parent_->domain_id(), item.addr(), address_mask);
}
pending->clear();
}
uint SecondLevelPageTable::pt_flags_to_mmu_flags(PtFlags flags, PageTableLevel level) {
uint mmu_flags = 0;
if (flags & kSlptRead) {
mmu_flags |= ARCH_MMU_FLAG_PERM_READ;
}
if (flags & kSlptWrite) {
mmu_flags |= ARCH_MMU_FLAG_PERM_WRITE;
}
if (flags & kSlptExecute) {
mmu_flags |= ARCH_MMU_FLAG_PERM_EXECUTE;
}
return mmu_flags;
}
} // namespace intel_iommu