| // 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 |
| |
| #ifndef ZIRCON_KERNEL_ARCH_X86_PAGE_TABLES_INCLUDE_ARCH_X86_PAGE_TABLES_PAGE_TABLES_H_ |
| #define ZIRCON_KERNEL_ARCH_X86_PAGE_TABLES_INCLUDE_ARCH_X86_PAGE_TABLES_PAGE_TABLES_H_ |
| |
| #include <align.h> |
| #include <lib/arch/x86/boot-cpuid.h> |
| #include <lib/fit/defer.h> |
| #include <lib/zx/result.h> |
| |
| #include <arch/x86/page_tables/constants.h> |
| #include <fbl/canary.h> |
| #include <hwreg/bitfields.h> |
| #include <kernel/mutex.h> |
| #include <page_tables/x86/constants.h> |
| #include <vm/arch_vm_aspace.h> |
| #include <vm/mapping_cursor.h> |
| #include <vm/physmap.h> |
| #include <vm/pmm.h> |
| |
| typedef uint64_t pt_entry_t; |
| #define PRIxPTE PRIx64 |
| |
| // Different page table levels in the page table mgmt hirerachy |
| enum class PageTableLevel { |
| PT_L = 0, |
| PD_L = 1, |
| PDP_L = 2, |
| PML4_L = 3, |
| }; |
| |
| // Different roles a page table can fulfill when running with unified aspaces. |
| enum class PageTableRole : uint8_t { |
| kIndependent, |
| kRestricted, |
| kShared, |
| kUnified, |
| }; |
| |
| // Type for flags used in the hardware page tables, for terminal entries. |
| // Note that some flags here may have meanings that depend on the level |
| // at which they occur (e.g. page size and PAT). |
| using PtFlags = uint64_t; |
| |
| // Type for flags used in the hardware page tables, for non-terminal |
| // entries. |
| using IntermediatePtFlags = uint64_t; |
| |
| namespace internal { |
| |
| // Utility for coalescing cache line flushes when modifying page tables. This |
| // allows us to mutate adjacent page table entries without having to flush for |
| // each cache line multiple times. |
| class CacheLineFlusher { |
| public: |
| // If |perform_invalidations| is false, this class acts as a no-op. |
| explicit CacheLineFlusher(bool perform_invalidations) |
| : dirty_line_(0), |
| cl_mask_(~(arch::BootCpuid<arch::CpuidProcessorInfo>().cache_line_size_bytes() - 1ull)), |
| perform_invalidations_(perform_invalidations) {} |
| ~CacheLineFlusher() { ForceFlush(); } |
| void FlushPtEntry(const volatile pt_entry_t* entry) { |
| uintptr_t entry_line = reinterpret_cast<uintptr_t>(entry) & cl_mask_; |
| if (entry_line != dirty_line_) { |
| ForceFlush(); |
| dirty_line_ = entry_line; |
| } |
| } |
| |
| void ForceFlush() { |
| if (dirty_line_ && perform_invalidations_) { |
| __asm__ volatile("clflush %0\n" : : "m"(*reinterpret_cast<char*>(dirty_line_)) : "memory"); |
| dirty_line_ = 0; |
| } |
| } |
| |
| private: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(CacheLineFlusher); |
| |
| // The cache-aligned address that currently dirty. If 0, no dirty line. |
| uintptr_t dirty_line_; |
| |
| const uintptr_t cl_mask_; |
| const bool perform_invalidations_; |
| }; |
| |
| // Structure for tracking an upcoming TLB invalidation |
| struct PendingTlbInvalidation { |
| struct Item { |
| uint64_t raw; |
| DEF_SUBFIELD(raw, 2, 0, page_level); |
| DEF_SUBBIT(raw, 3, is_global); |
| DEF_SUBBIT(raw, 4, is_terminal); |
| DEF_SUBFIELD(raw, 63, 12, encoded_addr); |
| |
| vaddr_t addr() const { return encoded_addr() << PAGE_SIZE_SHIFT; } |
| }; |
| static_assert(sizeof(Item) == 8, ""); |
| |
| // If true, ignore |vaddr| and perform a full invalidation for this context. |
| bool full_shootdown = false; |
| // If true, at least one enqueued entry was for a global page. |
| bool contains_global = false; |
| // Number of valid elements in |item| |
| uint count = 0; |
| // List of addresses queued for invalidation. |
| // Explicitly uninitialized since the size is fairly large. |
| Item item[32]; |
| |
| // Add address |v|, translated at depth |level|, to the set of addresses to be invalidated. |
| // |is_terminal| should be true iff this invalidation is targeting the final step of the |
| // translation rather than a higher page table entry. |is_global_page| should be true iff this |
| // page was mapped with the global bit set. |
| void enqueue(vaddr_t v, PageTableLevel level, bool is_global_page, bool is_terminal) { |
| if (is_global_page) { |
| contains_global = true; |
| } |
| |
| // We mark PML4_L entries as full shootdowns, since it's going to be |
| // expensive one way or another. |
| if (count >= ktl::size(item) || level == PageTableLevel::PML4_L) { |
| full_shootdown = true; |
| return; |
| } |
| item[count].set_page_level(static_cast<uint64_t>(level)); |
| item[count].set_is_global(is_global_page); |
| item[count].set_is_terminal(is_terminal); |
| item[count].set_encoded_addr(v >> PAGE_SIZE_SHIFT); |
| count++; |
| } |
| |
| // Clear the list of pending invalidations |
| void clear() { |
| count = 0; |
| full_shootdown = false; |
| contains_global = false; |
| } |
| |
| ~PendingTlbInvalidation() { DEBUG_ASSERT(count == 0); } |
| }; |
| |
| } // namespace internal |
| |
| class X86PageTableBase { |
| public: |
| X86PageTableBase() {} |
| virtual ~X86PageTableBase() { |
| DEBUG_ASSERT_MSG(!phys_, "page table dtor called before Destroy()"); |
| } |
| |
| paddr_t phys() const { return phys_; } |
| void* virt() const { return virt_; } |
| |
| size_t pages() { |
| Guard<Mutex> al{AssertOrderedLock, &lock_, LockOrder()}; |
| return pages_; |
| } |
| void* ctx() const { return ctx_; } |
| |
| using PendingTlbInvalidation = internal::PendingTlbInvalidation; |
| using CacheLineFlusher = internal::CacheLineFlusher; |
| |
| using ExistingEntryAction = ArchVmAspaceInterface::ExistingEntryAction; |
| using EnlargeOperation = ArchVmAspaceInterface::EnlargeOperation; |
| |
| // Returns whether this page table is restricted. |
| // We do so by verifying that it was created with `InitRestricted` and has been linked to a |
| // unified page table. |
| bool IsRestricted() const { return role_ == PageTableRole::kRestricted; } |
| |
| // Returns whether this page table is shared. |
| bool IsShared() const { return role_ == PageTableRole::kShared; } |
| |
| // Returns whether this page table is unified. |
| bool IsUnified() const { return role_ == PageTableRole::kUnified; } |
| |
| virtual zx_status_t MapPages(vaddr_t vaddr, paddr_t* phys, size_t count, uint mmu_flags, |
| ExistingEntryAction existing_action, size_t* mapped) = 0; |
| virtual zx_status_t MapPagesContiguous(vaddr_t vaddr, paddr_t paddr, const size_t count, |
| uint mmu_flags, size_t* mapped) = 0; |
| virtual zx_status_t UnmapPages(vaddr_t vaddr, const size_t count, EnlargeOperation enlarge, |
| size_t* unmapped) = 0; |
| virtual zx_status_t ProtectPages(vaddr_t vaddr, size_t count, uint mmu_flags) = 0; |
| virtual zx_status_t QueryVaddr(vaddr_t vaddr, paddr_t* paddr, uint* mmu_flags) = 0; |
| |
| using NonTerminalAction = ArchVmAspaceInterface::NonTerminalAction; |
| using TerminalAction = ArchVmAspaceInterface::TerminalAction; |
| virtual zx_status_t HarvestAccessed(vaddr_t vaddr, size_t count, |
| NonTerminalAction non_terminal_action, |
| TerminalAction terminal_action) = 0; |
| |
| // Returns 1 for unified page tables and 0 for all other page tables. This establishes an |
| // ordering that is used when the lock_ is acquired. The restricted page table lock is acquired |
| // first, and the unified page table lock is acquired afterwards. |
| uint32_t LockOrder() const { return IsUnified() ? 1 : 0; } |
| |
| protected: |
| using page_alloc_fn_t = ArchVmAspaceInterface::page_alloc_fn_t; |
| |
| // Initialize an empty page table, assigning this given context to it. |
| zx_status_t Init(void* ctx, page_alloc_fn_t test_paf = nullptr) TA_NO_THREAD_SAFETY_ANALYSIS { |
| test_page_alloc_func_ = test_paf; |
| |
| /* allocate a top level page table for the new address space */ |
| virt_ = AllocatePageTable(); |
| if (!virt_) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| phys_ = physmap_to_paddr(virt_); |
| DEBUG_ASSERT(phys_ != 0); |
| |
| ctx_ = ctx; |
| pages_ = 1; |
| return ZX_OK; |
| } |
| |
| pt_entry_t* AllocatePageTable() { |
| paddr_t pa; |
| vm_page* p; |
| zx_status_t status; |
| |
| // The default allocation routine is pmm_alloc_page so test and explicitly call it |
| // to avoid any unnecessary virtual function calls. |
| if (likely(!test_page_alloc_func_)) { |
| status = pmm_alloc_page(0, &p, &pa); |
| } else { |
| status = test_page_alloc_func_(0, &p, &pa); |
| } |
| if (status != ZX_OK) { |
| return nullptr; |
| } |
| p->set_state(vm_page_state::MMU); |
| |
| pt_entry_t* page_ptr = static_cast<pt_entry_t*>(paddr_to_physmap(pa)); |
| DEBUG_ASSERT(page_ptr); |
| |
| arch_zero_page(page_ptr); |
| |
| return page_ptr; |
| } |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(X86PageTableBase); |
| |
| fbl::Canary<fbl::magic("X86P")> canary_; |
| |
| // The number of times entries in the pml4 are referenced by other page tables. |
| // Unified page tables increment and decrement this value on their associated shared and |
| // restricted page tables, so we must hold the lock_ when doing so. |
| uint32_t num_references_ TA_GUARDED(lock_) = 0; |
| |
| // The role this page table plays in unified aspaces, if any. This should only be set by the |
| // Init* functions, and should not be modified anywhere else. |
| PageTableRole role_ = PageTableRole::kIndependent; |
| |
| // Page allocate function, overridable for testing. |
| page_alloc_fn_t test_page_alloc_func_ = nullptr; |
| |
| // Pointer to the translation table. |
| paddr_t phys_ = 0; |
| pt_entry_t* virt_ = nullptr; |
| |
| // Counter of pages allocated to back the translation table. |
| size_t pages_ TA_GUARDED(lock_) = 0; |
| |
| // A context structure that may used by a PageTable type above as part of |
| // invalidation. |
| void* ctx_ = nullptr; |
| |
| // Lock to protect the mmu code. |
| DECLARE_MUTEX(X86PageTableBase, lockdep::LockFlagsNestable) lock_; |
| }; |
| |
| // Implementation of the X86 page table code, that is expected to be derived using the recursive |
| // template pattern. The derived class T is expected to implement the following methods: |
| // |
| // Returns the highest level of the page tables |
| // PageTableLevel top_level(); |
| // |
| // Returns true if the given ARCH_MMU_FLAG_* flag combination is valid. |
| // bool allowed_flags(uint flags); |
| // |
| // Returns true if the given paddr is valid |
| // bool check_paddr(paddr_t paddr); |
| // |
| // Returns true if the given vaddr is valid |
| // bool check_vaddr(vaddr_t vaddr); |
| // |
| // Whether the processor supports the page size of this level |
| // bool supports_page_size(PageTableLevel level); |
| // |
| // Return the hardware flags to use on intermediate page tables entries |
| // IntermediatePtFlags intermediate_flags(); |
| // |
| // Return the hardware flags to use on terminal page table entries |
| // PtFlags terminal_flags(PageTableLevel level, uint flags); |
| // |
| // Return the hardware flags to use on smaller pages after a splitting a |
| // large page with flags |flags|. |
| // PtFlags split_flags(PageTableLevel level, PtFlags flags); |
| // |
| // Execute the given pending invalidation |
| // void TlbInvalidate(const PendingTlbInvalidation* pending); |
| // |
| // Convert PtFlags to ARCH_MMU_* flags. |
| // uint pt_flags_to_mmu_flags(PtFlags flags, PageTableLevel level); |
| // |
| // Returns true if a cache flush is necessary for pagetable changes to be |
| // visible to hardware page table walkers. On x86, this is only true for Intel IOMMU page |
| // tables when the IOMMU 'caching mode' bit is true. |
| // bool needs_cache_flushes(); |
| template <class T> |
| class X86PageTableImpl : public X86PageTableBase { |
| public: |
| X86PageTableImpl() {} |
| |
| // Accessors for the shared and restricted page tables on a unified page table. |
| // We can turn off thread safety analysis as these accessors should only be used on unified page |
| // tables, for which both the shared and restricted page table pointers are notionally const. |
| X86PageTableBase* get_shared_pt() TA_NO_THREAD_SAFETY_ANALYSIS { |
| DEBUG_ASSERT(IsUnified()); |
| return shared_pt_; |
| } |
| X86PageTableBase* get_restricted_pt() TA_NO_THREAD_SAFETY_ANALYSIS { |
| DEBUG_ASSERT(IsUnified()); |
| return referenced_pt_; |
| } |
| |
| zx_status_t MapPages(vaddr_t vaddr, paddr_t* phys, size_t count, uint mmu_flags, |
| ExistingEntryAction existing_action, size_t* mapped) override final { |
| canary_.Assert(); |
| |
| if (!static_cast<T*>(this)->check_vaddr(vaddr)) |
| return ZX_ERR_INVALID_ARGS; |
| for (size_t i = 0; i < count; ++i) { |
| if (!static_cast<T*>(this)->check_paddr(phys[i])) |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (count == 0) |
| return ZX_OK; |
| |
| if (!static_cast<T*>(this)->allowed_flags(mmu_flags)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| __UNINITIALIZED ConsistencyManager cm(this); |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| DEBUG_ASSERT(virt_); |
| |
| MappingCursor cursor(/*paddrs=*/phys, /*paddr_count=*/count, /*page_size=*/PAGE_SIZE, |
| /*vaddr=*/vaddr); |
| zx_status_t status = AddMapping(virt_, mmu_flags, static_cast<T*>(this)->top_level(), |
| existing_action, cursor, &cm); |
| cm.Finish(); |
| if (status != ZX_OK) { |
| dprintf(SPEW, "Add mapping failed with err=%d\n", status); |
| return status; |
| } |
| DEBUG_ASSERT(cursor.size() == 0); |
| } |
| |
| if (mapped) { |
| *mapped = count; |
| } |
| return ZX_OK; |
| } |
| zx_status_t MapPagesContiguous(vaddr_t vaddr, paddr_t paddr, const size_t count, uint mmu_flags, |
| size_t* mapped) override final { |
| canary_.Assert(); |
| |
| if (!static_cast<T*>(this)->check_paddr(paddr)) |
| return ZX_ERR_INVALID_ARGS; |
| if (!static_cast<T*>(this)->check_vaddr(vaddr)) |
| return ZX_ERR_INVALID_ARGS; |
| if (count == 0) |
| return ZX_OK; |
| |
| if (!static_cast<T*>(this)->allowed_flags(mmu_flags)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| MappingCursor cursor(/*paddrs=*/&paddr, /*paddr_count=*/1, /*page_size=*/count * PAGE_SIZE, |
| /*vaddr=*/vaddr); |
| __UNINITIALIZED ConsistencyManager cm(this); |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| DEBUG_ASSERT(virt_); |
| zx_status_t status = AddMapping(virt_, mmu_flags, static_cast<T*>(this)->top_level(), |
| ExistingEntryAction::Error, cursor, &cm); |
| cm.Finish(); |
| if (status != ZX_OK) { |
| dprintf(SPEW, "Add mapping failed with err=%d\n", status); |
| return status; |
| } |
| } |
| DEBUG_ASSERT(cursor.size() == 0); |
| |
| if (mapped) |
| *mapped = count; |
| |
| return ZX_OK; |
| } |
| zx_status_t UnmapPages(vaddr_t vaddr, const size_t count, EnlargeOperation enlarge, |
| size_t* unmapped) override final { |
| canary_.Assert(); |
| |
| if (!static_cast<T*>(this)->check_vaddr(vaddr)) |
| return ZX_ERR_INVALID_ARGS; |
| if (count == 0) |
| return ZX_OK; |
| |
| MappingCursor cursor(/*vaddr=*/vaddr, /*size=*/count * PAGE_SIZE); |
| |
| __UNINITIALIZED ConsistencyManager cm(this); |
| // This needs to be initialized to some value as gcc cannot work out that it can elide the |
| // default constructor. |
| zx::result<bool> status = zx::ok(true); |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| DEBUG_ASSERT(virt_); |
| status = RemoveMapping(virt_, static_cast<T*>(this)->top_level(), enlarge, cursor, &cm); |
| cm.Finish(); |
| } |
| DEBUG_ASSERT(cursor.size() == 0 || status.is_error()); |
| |
| if (unmapped) |
| *unmapped = count; |
| |
| return status.status_value(); |
| } |
| zx_status_t ProtectPages(vaddr_t vaddr, size_t count, uint mmu_flags) override final { |
| canary_.Assert(); |
| |
| if (!static_cast<T*>(this)->check_vaddr(vaddr)) |
| return ZX_ERR_INVALID_ARGS; |
| if (count == 0) |
| return ZX_OK; |
| |
| if (!static_cast<T*>(this)->allowed_flags(mmu_flags)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| MappingCursor cursor(/*vaddr=*/vaddr, /*size=*/count * PAGE_SIZE); |
| __UNINITIALIZED ConsistencyManager cm(this); |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| zx_status_t status = |
| UpdateMapping(virt_, mmu_flags, static_cast<T*>(this)->top_level(), cursor, &cm); |
| cm.Finish(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| DEBUG_ASSERT(cursor.size() == 0); |
| return ZX_OK; |
| } |
| |
| zx_status_t QueryVaddr(vaddr_t vaddr, paddr_t* paddr, uint* mmu_flags) override final { |
| canary_.Assert(); |
| |
| PageTableLevel ret_level; |
| |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| |
| volatile pt_entry_t* last_valid_entry; |
| zx_status_t status = |
| GetMapping(virt_, vaddr, static_cast<T*>(this)->top_level(), &ret_level, &last_valid_entry); |
| if (status != ZX_OK) |
| return status; |
| |
| DEBUG_ASSERT(last_valid_entry); |
| |
| /* based on the return level, parse the page table entry */ |
| if (paddr) { |
| switch (ret_level) { |
| case PageTableLevel::PDP_L: /* 1GB page */ |
| *paddr = paddr_from_pte(PageTableLevel::PDP_L, *last_valid_entry); |
| *paddr |= vaddr & PAGE_OFFSET_MASK_HUGE; |
| break; |
| case PageTableLevel::PD_L: /* 2MB page */ |
| *paddr = paddr_from_pte(PageTableLevel::PD_L, *last_valid_entry); |
| *paddr |= vaddr & PAGE_OFFSET_MASK_LARGE; |
| break; |
| case PageTableLevel::PT_L: /* 4K page */ |
| *paddr = paddr_from_pte(PageTableLevel::PT_L, *last_valid_entry); |
| *paddr |= vaddr & PAGE_OFFSET_MASK_4KB; |
| break; |
| default: |
| panic("arch_mmu_query: unhandled frame level\n"); |
| } |
| } |
| |
| /* converting arch-specific flags to mmu flags */ |
| if (mmu_flags) { |
| *mmu_flags = static_cast<T*>(this)->pt_flags_to_mmu_flags(*last_valid_entry, ret_level); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t HarvestAccessed(vaddr_t vaddr, size_t count, NonTerminalAction non_terminal_action, |
| TerminalAction terminal_action) override final { |
| canary_.Assert(); |
| |
| if (!static_cast<T*>(this)->check_vaddr(vaddr)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (count == 0) { |
| return ZX_OK; |
| } |
| |
| MappingCursor cursor(/*vaddr=*/vaddr, /*size=*/count * PAGE_SIZE); |
| __UNINITIALIZED ConsistencyManager cm(this); |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| HarvestMapping(virt_, non_terminal_action, terminal_action, |
| static_cast<T*>(this)->top_level(), cursor, &cm); |
| cm.Finish(); |
| } |
| DEBUG_ASSERT(cursor.size() == 0); |
| return ZX_OK; |
| } |
| |
| protected: |
| // We disable analysis due to the write to |pages_| tripping it up. It is safe |
| // to write to |pages_| since this is part of object construction. |
| // Initialize an empty page table and mark it as restricted. |
| zx_status_t InitRestricted(void* ctx, page_alloc_fn_t test_paf = nullptr) { |
| role_ = PageTableRole::kRestricted; |
| return Init(ctx, test_paf); |
| } |
| // Initialize a page table, assign the given context, and prepopulate the top level page table |
| // entries. |
| // We disable analysis due to the write to |pages_| tripping it up. It is safe |
| // to write to |pages_| since this is part of object construction. |
| zx_status_t InitShared(void* ctx, vaddr_t base, size_t size, |
| page_alloc_fn_t test_paf = nullptr) TA_NO_THREAD_SAFETY_ANALYSIS { |
| zx_status_t status = Init(ctx, test_paf); |
| if (status != ZX_OK) { |
| return status; |
| } |
| role_ = PageTableRole::kShared; |
| |
| PageTableLevel top = static_cast<T*>(this)->top_level(); |
| const uint start = vaddr_to_index(top, base); |
| uint end = vaddr_to_index(top, base + size - 1); |
| // Check the end if it fills out the table entry. |
| if (page_aligned(top, base + size)) { |
| end += 1; |
| } |
| IntermediatePtFlags flags = static_cast<T*>(this)->intermediate_flags(); |
| |
| for (uint i = start; i < end; i++) { |
| pt_entry_t* pdp = AllocatePageTable(); |
| if (pdp == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| pages_ += 1; |
| virt_[i] = X86_VIRT_TO_PHYS(pdp) | flags | X86_MMU_PG_P; |
| } |
| return ZX_OK; |
| } |
| |
| // Initialize a page table, assign the given context, and set it up as a unified page table with |
| // entries from the given page tables. |
| // |
| // The shared and restricted page tables must satisfy the following requirements: |
| // 1) The shared page table must set only |is_shared_| to true. |
| // 2) The restricted page table must set only |is_restricted_| to true. |
| // 3) Both the shared and restricted page tables must have been initialized prior to this call. |
| zx_status_t InitUnified(void* ctx, X86PageTableImpl<T>* shared, vaddr_t shared_base, |
| size_t shared_size, X86PageTableImpl<T>* restricted, |
| vaddr_t restricted_base, size_t restricted_size, |
| page_alloc_fn_t test_paf = nullptr) { |
| DEBUG_ASSERT(restricted->IsRestricted()); |
| DEBUG_ASSERT(shared->IsShared()); |
| // Validate that the shared and restricted page tables do not overlap and do not share a PML4 |
| // entry. |
| PageTableLevel top = static_cast<T*>(this)->top_level(); |
| const uint restricted_start = vaddr_to_index(top, restricted_base); |
| uint restricted_end = vaddr_to_index(top, restricted_base + restricted_size - 1); |
| if (page_aligned(top, restricted_base + restricted_size)) { |
| restricted_end += 1; |
| } |
| const uint shared_start = vaddr_to_index(top, shared_base); |
| uint shared_end = vaddr_to_index(top, shared_base + shared_size - 1); |
| if (page_aligned(top, shared_base + shared_size)) { |
| shared_end += 1; |
| } |
| DEBUG_ASSERT(restricted_end <= shared_start); |
| |
| zx_status_t status = Init(ctx, test_paf); |
| if (status != ZX_OK) { |
| return status; |
| } |
| role_ = PageTableRole::kUnified; |
| |
| // Validate the restricted page table and set its metadata. |
| { |
| Guard<Mutex> a{AssertOrderedLock, &restricted->lock_, restricted->LockOrder()}; |
| DEBUG_ASSERT(restricted->virt_); |
| DEBUG_ASSERT(restricted->referenced_pt_ == nullptr); |
| |
| // Assert that there are no entries in the restricted page table. |
| for (uint i = restricted_start; i < restricted_end; i++) { |
| DEBUG_ASSERT(!IS_PAGE_PRESENT(restricted->virt_[i])); |
| } |
| |
| restricted->referenced_pt_ = this; |
| restricted->num_references_++; |
| } |
| |
| // Copy all mappings from the shared page table and set its metadata. |
| { |
| Guard<Mutex> a{AssertOrderedLock, &shared->lock_, shared->LockOrder()}; |
| DEBUG_ASSERT(shared->virt_); |
| DEBUG_ASSERT(shared->referenced_pt_ == nullptr); |
| |
| // Set up the PML4 so we capture any mappings created prior to creation of this unified page |
| // table. |
| pt_entry_t curr_entry = 0; |
| for (uint i = shared_start; i < shared_end; i++) { |
| curr_entry = shared->virt_[i]; |
| if (IS_PAGE_PRESENT(curr_entry)) { |
| virt_[i] = curr_entry; |
| } |
| } |
| shared->num_references_++; |
| } |
| |
| // Update this page table's bookkeeping. |
| { |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| referenced_pt_ = restricted; |
| shared_pt_ = shared; |
| } |
| return ZX_OK; |
| } |
| |
| // Calls DestroyUnified if this is a unified page table and DestroyIndividual if it is not. |
| void Destroy(vaddr_t base, size_t size) { |
| canary_.Assert(); |
| if (IsUnified()) { |
| return DestroyUnified(); |
| } |
| return DestroyIndividual(base, size); |
| } |
| |
| private: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(X86PageTableImpl); |
| |
| // Utility for managing consistency of the page tables from a cache and TLB |
| // point-of-view. It ensures that memory is not freed while a TLB entry may |
| // refer to it, and that changes to the page tables have appropriate visiblity |
| // to the hardware interpreting them. Finish MUST be called on this |
| // class, even if the page table change failed. |
| // The aspace lock *must* be held over the full operation of the ConsistencyManager, from |
| // queue_free to Flush. The lock must be held continuously, due to strategy employed here of only |
| // invalidating actual vaddrs with changing entries, and not all vaddrs an operation applies to. |
| // Otherwise the following scenario is possible |
| // 1. Thread 1 performs an Unmap and removes PTE entries, but drops the lock prior to |
| // invalidation. |
| // 2. Thread 2 performs an Unmap, no PTE entries are removed, no invalidations occur |
| // 3. Thread 2 now believes the resources (pages) for the region are no longer accessible, and |
| // returns them to the pmm. |
| // 4. Thread 3 attempts to access this region and is now able to read/write to returned pages as |
| // invalidations have not occurred. |
| // This scenario is possible as the mappings here are not the source of truth of resource |
| // management, but a cache of information from other parts of the system. If thread 2 wanted to |
| // guarantee that the pages were free it could issue it's own TLB invalidations for the vaddr |
| // range, even though it found no entries. However this is not the trategy employed here at the |
| // moment. |
| class ConsistencyManager { |
| public: |
| explicit ConsistencyManager(X86PageTableImpl<T>* pt) |
| : pt_(pt), clf_(static_cast<T*>(pt)->needs_cache_flushes()) {} |
| ~ConsistencyManager() { |
| DEBUG_ASSERT(pt_ == nullptr); |
| |
| // We free the paging structures here rather than in Finish(), to allow |
| // support deferring invoking pmm_free() until after we've left the page |
| // table lock. |
| vm_page_t* p; |
| list_for_every_entry (&to_free_, p, vm_page_t, queue_node) { |
| DEBUG_ASSERT(p->state() == vm_page_state::MMU); |
| } |
| if (!list_is_empty(&to_free_)) { |
| pmm_free(&to_free_); |
| } |
| } |
| |
| void queue_free(vm_page_t* page) { |
| AssertHeld(pt_->lock_); |
| DEBUG_ASSERT(page->state() == vm_page_state::MMU); |
| list_add_tail(&to_free_, &page->queue_node); |
| DEBUG_ASSERT(pt_->pages_ > 0); |
| pt_->pages_--; |
| } |
| |
| CacheLineFlusher* cache_line_flusher() { return &clf_; } |
| PendingTlbInvalidation* pending_tlb() { return &tlb_; } |
| |
| // This function must be called while holding pt_->lock_. |
| void Finish() { |
| AssertHeld(pt_->lock_); |
| |
| clf_.ForceFlush(); |
| if (static_cast<T*>(pt_)->needs_cache_flushes()) { |
| // If the hardware needs cache flushes for the tables to be visible, |
| // make sure we serialize the flushes before issuing the TLB |
| // invalidations. |
| arch::DeviceMemoryBarrier(); |
| } |
| static_cast<T*>(pt_)->TlbInvalidate(&tlb_); |
| if (pt_->IsRestricted() && pt_->referenced_pt_ != nullptr) { |
| // TODO(https://fxbug.dev/42083004): This TLB invalidation could be wrapped into the |
| // preceding one so long as we built the target mask correctly. |
| Guard<Mutex> a{AssertOrderedLock, &pt_->referenced_pt_->lock_, |
| pt_->referenced_pt_->LockOrder()}; |
| static_cast<T*>(pt_->referenced_pt_)->TlbInvalidate(&tlb_); |
| } |
| // Clear out the pending TLB invalidations. |
| tlb_.clear(); |
| pt_ = nullptr; |
| } |
| |
| void SetFullShootdown() { tlb_.full_shootdown = true; } |
| |
| private: |
| X86PageTableImpl<T>* pt_; |
| |
| // Cache line to flush prior to TLB invalidations |
| CacheLineFlusher clf_; |
| |
| // TLB invalidations that need to occur |
| PendingTlbInvalidation tlb_; |
| |
| // vm_page_t's to relese to the PMM after the TLB invalidation occurs |
| list_node to_free_ = LIST_INITIAL_VALUE(to_free_); |
| }; |
| |
| // given a page table entry, return a pointer to the next page table one level down |
| static inline volatile pt_entry_t* get_next_table_from_entry(pt_entry_t entry) { |
| if (!IS_PAGE_PRESENT(entry) || IS_LARGE_PAGE(entry)) |
| return nullptr; |
| |
| return reinterpret_cast<volatile pt_entry_t*>(X86_PHYS_TO_VIRT(entry & X86_PG_FRAME)); |
| } |
| |
| // Return the page size for this level |
| static size_t page_size(PageTableLevel level) { |
| switch (level) { |
| case PageTableLevel::PT_L: |
| return 1ULL << PT_SHIFT; |
| case PageTableLevel::PD_L: |
| return 1ULL << PD_SHIFT; |
| case PageTableLevel::PDP_L: |
| return 1ULL << PDP_SHIFT; |
| case PageTableLevel::PML4_L: |
| return 1ULL << PML4_SHIFT; |
| default: |
| panic("page_size: invalid level\n"); |
| } |
| } |
| |
| // Whether an address is aligned to the page size of this level |
| static bool page_aligned(PageTableLevel level, vaddr_t vaddr) { |
| return (vaddr & (page_size(level) - 1)) == 0; |
| } |
| |
| // Extract the index needed for finding |vaddr| for the given level |
| static uint vaddr_to_index(PageTableLevel level, vaddr_t vaddr) { |
| switch (level) { |
| case PageTableLevel::PML4_L: |
| return VADDR_TO_PML4_INDEX(vaddr); |
| case PageTableLevel::PDP_L: |
| return VADDR_TO_PDP_INDEX(vaddr); |
| case PageTableLevel::PD_L: |
| return VADDR_TO_PD_INDEX(vaddr); |
| case PageTableLevel::PT_L: |
| return VADDR_TO_PT_INDEX(vaddr); |
| default: |
| panic("vaddr_to_index: invalid level\n"); |
| } |
| } |
| |
| // Convert a PTE to a physical address |
| static paddr_t paddr_from_pte(PageTableLevel level, pt_entry_t pte) { |
| DEBUG_ASSERT(IS_PAGE_PRESENT(pte)); |
| |
| paddr_t pa; |
| switch (level) { |
| case PageTableLevel::PDP_L: |
| pa = (pte & X86_HUGE_PAGE_FRAME); |
| break; |
| case PageTableLevel::PD_L: |
| pa = (pte & X86_LARGE_PAGE_FRAME); |
| break; |
| case PageTableLevel::PT_L: |
| pa = (pte & X86_PG_FRAME); |
| break; |
| default: |
| panic("paddr_from_pte at unhandled level %d\n", static_cast<int>(level)); |
| } |
| |
| return pa; |
| } |
| |
| static PageTableLevel lower_level(PageTableLevel level) { |
| DEBUG_ASSERT(level != PageTableLevel::PT_L); |
| return static_cast<PageTableLevel>(static_cast<int>(level) - 1); |
| } |
| |
| static bool page_table_is_clear(const volatile pt_entry_t* page_table) { |
| uint lower_idx; |
| for (lower_idx = 0; lower_idx < NO_OF_PT_ENTRIES; ++lower_idx) { |
| if (IS_PAGE_PRESENT(page_table[lower_idx])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @brief Creates mappings for the range specified by start_cursor |
| * |
| * `level` must be top_level() when invoked from external code. |
| * |
| * @param table The current paging structure's virtual address. |
| * @param mmu_flags MMU flags describing attributes of the mapping. |
| * @param level Page table level which the current `table` is located at. |
| * @param existing_action Action to take if a mapping is already present. |
| * @param cursor A cursor describing the range of address space to act on. |
| * @param cm Object to manage consistency of page table entries and cache+TLB. |
| * |
| * @return ZX_OK if successful |
| * @return ZX_ERR_ALREADY_EXISTS if the range overlaps an existing mapping and |
| * `existing_action` is set to `Error` |
| * @return ZX_ERR_NO_MEMORY if intermediate page tables could not be allocated |
| */ |
| zx_status_t AddMapping(volatile pt_entry_t* table, uint mmu_flags, PageTableLevel level, |
| ExistingEntryAction existing_action, MappingCursor& cursor, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(table); |
| DEBUG_ASSERT(static_cast<T*>(this)->check_vaddr(cursor.vaddr())); |
| DEBUG_ASSERT(static_cast<T*>(this)->check_paddr(cursor.paddr())); |
| const vaddr_t start_vaddr = cursor.vaddr(); |
| // Unified page tables should never be mapping entries directly; rather, their constituent page |
| // tables should be mapping entries on their behalf. |
| DEBUG_ASSERT(!IsUnified()); |
| |
| zx_status_t ret = ZX_OK; |
| |
| if (level == PageTableLevel::PT_L) { |
| return AddMappingL0(table, mmu_flags, existing_action, cursor, cm); |
| } |
| |
| auto abort = fit::defer([&]() { |
| AssertHeld(lock_); |
| if (level == static_cast<T*>(this)->top_level()) { |
| // Build an unmap cursor. cursor.size should be how much is left to be mapped still. |
| MappingCursor unmap_cursor(/*vaddr=*/start_vaddr, |
| /*size=*/cursor.vaddr() - start_vaddr); |
| if (unmap_cursor.size() > 0) { |
| auto status = RemoveMapping(table, level, EnlargeOperation::No, unmap_cursor, cm); |
| // Removing the exact mappings we just added should never be able to fail. |
| ASSERT(status.is_ok()); |
| DEBUG_ASSERT(unmap_cursor.size() == 0); |
| } |
| } |
| }); |
| |
| IntermediatePtFlags interm_flags = static_cast<T*>(this)->intermediate_flags(); |
| PtFlags term_flags = static_cast<T*>(this)->terminal_flags(level, mmu_flags); |
| |
| size_t ps = page_size(level); |
| bool level_supports_large_pages = static_cast<T*>(this)->supports_page_size(level); |
| uint index = vaddr_to_index(level, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| |
| // See if there's a large page in our way |
| if (IS_PAGE_PRESENT(pt_val) && IS_LARGE_PAGE(pt_val)) { |
| if (existing_action == ExistingEntryAction::Error) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| cursor.ConsumePAddr(ps); |
| continue; |
| } |
| |
| // Check if this is a candidate for a new large page |
| bool level_valigned = page_aligned(level, cursor.vaddr()); |
| bool level_paligned = page_aligned(level, cursor.paddr()); |
| if (level_supports_large_pages && !IS_PAGE_PRESENT(pt_val) && level_valigned && |
| level_paligned && cursor.PageRemaining() >= ps) { |
| UpdateEntry(cm, level, cursor.vaddr(), table + index, cursor.paddr(), |
| term_flags | X86_MMU_PG_PS, /*was_terminal=*/false); |
| cursor.ConsumePAddr(ps); |
| } else { |
| // See if we need to create a new table. |
| if (!IS_PAGE_PRESENT(pt_val)) { |
| // We should never need to do this in a shared PML4. |
| if (level == PageTableLevel::PML4_L) { |
| DEBUG_ASSERT(!IsShared()); |
| } |
| volatile pt_entry_t* m = AllocatePageTable(); |
| if (m == nullptr) { |
| // The mapping wasn't fully updated, but there is work here |
| // that might need to be undone. |
| size_t partial_update = ktl::min(ps, cursor.size()); |
| // Cancel paddr tracking so we account for the virtual range we need to |
| // unmap without needing to increment in page appropriate amounts. |
| cursor.DropPAddrs(); |
| cursor.ConsumeVAddr(partial_update); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if (level == PageTableLevel::PML4_L && IsRestricted() && referenced_pt_ != nullptr) { |
| Guard<Mutex> a{AssertOrderedLock, &referenced_pt_->lock_, referenced_pt_->LockOrder()}; |
| pt_entry_t* referenced_entry = (pt_entry_t*)referenced_pt_->virt() + index; |
| DEBUG_ASSERT(check_equal_ignore_flags(*referenced_entry, *e)); |
| |
| ConsistencyManager cm_referenced(referenced_pt_); |
| referenced_pt_->UpdateEntry(&cm_referenced, level, cursor.vaddr(), referenced_entry, |
| X86_VIRT_TO_PHYS(m), interm_flags, |
| /*was_terminal=*/false); |
| cm_referenced.Finish(); |
| } |
| |
| UpdateEntry(cm, level, cursor.vaddr(), e, X86_VIRT_TO_PHYS(m), interm_flags, |
| /*was_terminal=*/false); |
| |
| pt_val = *e; |
| pages_++; |
| } |
| |
| ret = AddMapping(get_next_table_from_entry(pt_val), mmu_flags, lower_level(level), |
| existing_action, cursor, cm); |
| |
| if (ret != ZX_OK) { |
| return ret; |
| } |
| } |
| } |
| abort.cancel(); |
| return ZX_OK; |
| } |
| |
| // Base case of AddMapping for smallest page size. |
| zx_status_t AddMappingL0(volatile pt_entry_t* table, uint mmu_flags, |
| ExistingEntryAction existing_action, MappingCursor& cursor, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(cursor.size())); |
| |
| const PtFlags term_flags = |
| static_cast<T*>(this)->terminal_flags(PageTableLevel::PT_L, mmu_flags); |
| uint index = vaddr_to_index(PageTableLevel::PT_L, cursor.vaddr()); |
| |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* existing_entry = table + index; |
| |
| if (IS_PAGE_PRESENT(*existing_entry)) { |
| if (existing_action == ExistingEntryAction::Upgrade) { |
| const paddr_t existing_paddr = (*existing_entry) & X86_PG_FRAME; |
| const bool remapping_same_address = existing_paddr == cursor.paddr(); |
| const bool mmu_flags_ro = |
| (mmu_flags & ARCH_MMU_FLAG_PERM_RWX_MASK) == ARCH_MMU_FLAG_PERM_READ; |
| |
| // If the physical page we are trying to map is already present, and |
| // we would be marking it read only, then don't. |
| // Either: |
| // 1. it is already read-only - we can skip the work. |
| // 2. it is already writable - we shouldn't downgrade permissions. |
| if (!remapping_same_address || !mmu_flags_ro) { |
| UpdateEntry(cm, PageTableLevel::PT_L, cursor.vaddr(), existing_entry, cursor.paddr(), |
| term_flags, /*was_terminal=*/false); |
| } |
| } else if (existing_action == ExistingEntryAction::Error) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| } else { |
| UpdateEntry(cm, PageTableLevel::PT_L, cursor.vaddr(), existing_entry, cursor.paddr(), |
| term_flags, /*was_terminal=*/false); |
| } |
| cursor.ConsumePAddr(PAGE_SIZE); |
| } |
| |
| return ZX_OK; |
| } |
| |
| /** |
| * @brief Unmaps the range specified by start_cursor. |
| * |
| * Level must be top_level() when invoked. The caller must, even on |
| * failure, free all pages in the |to_free| list and adjust the |pages_| count. |
| * |
| * @param table The top-level paging structure's virtual address. |
| * @param start_cursor A cursor describing the range of address space to |
| * unmap within table |
| * @param new_cursor A returned cursor describing how much work was not |
| * completed. Must be non-null. |
| * |
| * @return true if the caller (i.e. the next level up page table) might need to |
| * free this page table. |
| */ |
| zx::result<bool> RemoveMapping(volatile pt_entry_t* table, PageTableLevel level, |
| EnlargeOperation enlarge, MappingCursor& cursor, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(table); |
| DEBUG_ASSERT(static_cast<T*>(this)->check_vaddr(cursor.vaddr())); |
| // Unified page tables should never be unmapping entries directly; rather, their constituent |
| // page tables should be unmapping entries on their behalf. |
| DEBUG_ASSERT(!IsUnified()); |
| |
| if (level == PageTableLevel::PT_L) { |
| return zx::ok(RemoveMappingL0(table, cursor, cm)); |
| } |
| |
| bool unmapped = false; |
| // Track if there are any entries at all. This is necessary to properly rollback if an |
| // attempt to map a page fails to allocate a page table, as that case can result in an |
| // empty non-last-level page table. |
| bool any_pages = false; |
| size_t ps = page_size(level); |
| uint index = vaddr_to_index(level, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| // If the page isn't even mapped, just skip it |
| if (!IS_PAGE_PRESENT(pt_val)) { |
| cursor.SkipEntry(ps); |
| continue; |
| } |
| any_pages = true; |
| |
| if (IS_LARGE_PAGE(pt_val)) { |
| bool vaddr_level_aligned = page_aligned(level, cursor.vaddr()); |
| // If the request covers the entire large page, just unmap it |
| if (vaddr_level_aligned && cursor.size() >= ps) { |
| UnmapEntry(cm, level, cursor.vaddr(), e, /*was_terminal=*/true); |
| unmapped = true; |
| |
| cursor.ConsumeVAddr(ps); |
| continue; |
| } |
| // Otherwise, we need to split it |
| vaddr_t page_vaddr = cursor.vaddr() & ~(ps - 1); |
| zx_status_t status = SplitLargePage(level, page_vaddr, e, cm); |
| if (status != ZX_OK) { |
| // If split fails, just unmap the whole thing, and let a |
| // subsequent page fault clean it up. |
| if (enlarge == EnlargeOperation::Yes) { |
| UnmapEntry(cm, level, cursor.vaddr(), e, /*was_terminal=*/true); |
| unmapped = true; |
| |
| cursor.SkipEntry(ps); |
| continue; |
| } else { |
| return zx::error(status); |
| } |
| } |
| pt_val = *e; |
| } |
| |
| volatile pt_entry_t* next_table = get_next_table_from_entry(pt_val); |
| // Remember where we are unmapping from in case we need to do a second pass to remove a PT. |
| const vaddr_t unmap_vaddr = cursor.vaddr(); |
| auto status = RemoveMapping(next_table, lower_level(level), enlarge, cursor, cm); |
| if (status.is_error()) { |
| return status; |
| } |
| const size_t unmap_size = cursor.vaddr() - unmap_vaddr; |
| bool lower_unmapped = status.value(); |
| |
| // If we were requesting to unmap everything in the lower page table, |
| // we know we can unmap the lower level page table. Otherwise, if |
| // we unmapped anything in the lower level, check to see if that |
| // level is now empty. |
| bool unmap_page_table = page_aligned(level, unmap_vaddr) && unmap_size >= ps; |
| // If the top level page is shared, we cannot unmap it here as other page tables may be |
| // referencing its entries. |
| if (IsShared() && level == PageTableLevel::PML4_L) { |
| unmap_page_table = false; |
| } else if (!unmap_page_table && lower_unmapped) { |
| unmap_page_table = page_table_is_clear(next_table); |
| } |
| if (unmap_page_table) { |
| paddr_t ptable_phys = X86_VIRT_TO_PHYS(next_table); |
| |
| vm_page_t* page = paddr_to_vm_page(ptable_phys); |
| if (level == PageTableLevel::PML4_L && IsRestricted() && referenced_pt_ != nullptr) { |
| Guard<Mutex> a{AssertOrderedLock, &referenced_pt_->lock_, referenced_pt_->LockOrder()}; |
| pt_entry_t* referenced_entry = (pt_entry_t*)referenced_pt_->virt() + index; |
| DEBUG_ASSERT(check_equal_ignore_flags(*referenced_entry, *e)); |
| |
| ConsistencyManager cm_referenced(referenced_pt_); |
| referenced_pt_->UnmapEntry(&cm_referenced, level, unmap_vaddr, referenced_entry, false); |
| cm_referenced.Finish(); |
| } |
| UnmapEntry(cm, level, unmap_vaddr, e, /*was_terminal=*/false); |
| |
| DEBUG_ASSERT(page); |
| DEBUG_ASSERT_MSG(page->state() == vm_page_state::MMU, |
| "page %p state %u, paddr %#" PRIxPTR "\n", page, |
| static_cast<uint32_t>(page->state()), X86_VIRT_TO_PHYS(next_table)); |
| DEBUG_ASSERT(!list_in_list(&page->queue_node)); |
| |
| cm->queue_free(page); |
| unmapped = true; |
| } |
| |
| DEBUG_ASSERT(cursor.size() == 0 || page_aligned(level, cursor.vaddr())); |
| } |
| |
| return zx::ok(unmapped || !any_pages); |
| } |
| // Base case of RemoveMapping for smallest page size. |
| bool RemoveMappingL0(volatile pt_entry_t* table, MappingCursor& cursor, ConsistencyManager* cm) |
| TA_REQ(lock_) { |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(cursor.size())); |
| |
| bool unmapped = false; |
| uint index = vaddr_to_index(PageTableLevel::PT_L, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| if (IS_PAGE_PRESENT(*e)) { |
| UnmapEntry(cm, PageTableLevel::PT_L, cursor.vaddr(), e, /*was_terminal=*/true); |
| unmapped = true; |
| } |
| |
| cursor.ConsumeVAddr(PAGE_SIZE); |
| } |
| return unmapped; |
| } |
| |
| /** |
| * @brief Changes the permissions/caching of the range specified by start_cursor |
| * |
| * Level must be top_level() when invoked. The caller must, even on |
| * failure, free all pages in the |to_free| list and adjust the |pages_| count. |
| * |
| * @param table The top-level paging structure's virtual address. |
| * @param start_cursor A cursor describing the range of address space to |
| * act on within table |
| * @param new_cursor A returned cursor describing how much work was not |
| * completed. Must be non-null. |
| */ |
| zx_status_t UpdateMapping(volatile pt_entry_t* table, uint mmu_flags, PageTableLevel level, |
| MappingCursor& cursor, ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(table); |
| DEBUG_ASSERT(static_cast<T*>(this)->check_vaddr(cursor.vaddr())); |
| |
| if (level == PageTableLevel::PT_L) { |
| return UpdateMappingL0(table, mmu_flags, cursor, cm); |
| } |
| |
| zx_status_t ret = ZX_OK; |
| |
| PtFlags term_flags = static_cast<T*>(this)->terminal_flags(level, mmu_flags); |
| |
| size_t ps = page_size(level); |
| uint index = vaddr_to_index(level, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| // Skip unmapped pages (we may encounter these due to demand paging) |
| if (!IS_PAGE_PRESENT(pt_val)) { |
| cursor.SkipEntry(ps); |
| continue; |
| } |
| |
| if (IS_LARGE_PAGE(pt_val)) { |
| bool vaddr_level_aligned = page_aligned(level, cursor.vaddr()); |
| // If the request covers the entire large page, just change the |
| // permissions |
| if (vaddr_level_aligned && cursor.size() >= ps) { |
| UpdateEntry(cm, level, cursor.vaddr(), e, paddr_from_pte(level, pt_val), |
| term_flags | X86_MMU_PG_PS, /*was_terminal=*/true); |
| cursor.ConsumeVAddr(ps); |
| continue; |
| } |
| // Otherwise, we need to split it |
| vaddr_t page_vaddr = cursor.vaddr() & ~(ps - 1); |
| ret = SplitLargePage(level, page_vaddr, e, cm); |
| if (ret != ZX_OK) { |
| return ret; |
| } |
| pt_val = *e; |
| } |
| |
| volatile pt_entry_t* next_table = get_next_table_from_entry(pt_val); |
| ret = UpdateMapping(next_table, mmu_flags, lower_level(level), cursor, cm); |
| if (ret != ZX_OK) { |
| return ret; |
| } |
| DEBUG_ASSERT(cursor.size() == 0 || page_aligned(level, cursor.vaddr())); |
| } |
| return ZX_OK; |
| } |
| // Base case of UpdateMapping for smallest page size. |
| zx_status_t UpdateMappingL0(volatile pt_entry_t* table, uint mmu_flags, MappingCursor& cursor, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(cursor.size())); |
| |
| PtFlags term_flags = static_cast<T*>(this)->terminal_flags(PageTableLevel::PT_L, mmu_flags); |
| |
| uint index = vaddr_to_index(PageTableLevel::PT_L, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| // Skip unmapped pages (we may encounter these due to demand paging) |
| if (IS_PAGE_PRESENT(pt_val)) { |
| UpdateEntry(cm, PageTableLevel::PT_L, cursor.vaddr(), e, |
| paddr_from_pte(PageTableLevel::PT_L, pt_val), term_flags, |
| /*was_terminal=*/true); |
| } |
| |
| cursor.ConsumeVAddr(PAGE_SIZE); |
| } |
| DEBUG_ASSERT(cursor.size() == 0 || page_aligned(PageTableLevel::PT_L, cursor.vaddr())); |
| return ZX_OK; |
| } |
| /** |
| * @brief Removes the accessed flag on any terminal entries and calls |
| * pmm_page_queues()->MarkAccessed on them. For non-terminal entries any accessed bits are |
| * harvested, and unaccessed non-terminal entries are unmapped or retained based on the passed in |
| * action. |
| * |
| * Level must be top_level() when invoked. The caller must, even on |
| * failure, free all pages in the |to_free| list and adjust the |pages_| count. |
| * |
| * @param table The top-level paging structure's virtual address. |
| * @param start_cursor A cursor describing the range of address space to |
| * act on within table |
| * @param new_cursor A returned cursor describing how much work was not |
| * completed. Must be non-null. |
| * |
| * @return true if the caller (i.e. the next level up page table) might need to |
| * free this page table. |
| */ |
| bool HarvestMapping(volatile pt_entry_t* table, NonTerminalAction non_terminal_action, |
| TerminalAction terminal_action, PageTableLevel level, MappingCursor& cursor, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(table); |
| DEBUG_ASSERT(static_cast<T*>(this)->check_vaddr(cursor.vaddr())); |
| |
| if (level == PageTableLevel::PT_L) { |
| HarvestMappingL0(table, terminal_action, cursor, cm); |
| // HarvestMappingL0 never actually unmaps any entries, so this is always false. |
| return false; |
| } |
| |
| // Track if we perform any unmappings. We propagate this up to our caller, since if we performed |
| // any unmappings then we could now be empty, and if so our caller needs to free us. |
| bool unmapped = false; |
| size_t ps = page_size(level); |
| uint index = vaddr_to_index(level, cursor.vaddr()); |
| bool always_recurse = level == PageTableLevel::PML4_L && (IsShared() || IsRestricted()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| // If the page isn't even mapped, just skip it |
| if (!IS_PAGE_PRESENT(pt_val)) { |
| cursor.SkipEntry(ps); |
| continue; |
| } |
| |
| if (IS_LARGE_PAGE(pt_val)) { |
| bool vaddr_level_aligned = page_aligned(level, cursor.vaddr()); |
| // If the request covers the entire large page then harvest the accessed bit, otherwise we |
| // just skip it. |
| if (vaddr_level_aligned && cursor.size() >= ps) { |
| const uint mmu_flags = static_cast<T*>(this)->pt_flags_to_mmu_flags(pt_val, level); |
| const PtFlags term_flags = static_cast<T*>(this)->terminal_flags(level, mmu_flags); |
| UpdateEntry(cm, level, cursor.vaddr(), e, paddr_from_pte(level, pt_val), |
| term_flags | X86_MMU_PG_PS, /*was_terminal=*/true, /*exact_flags=*/true); |
| } |
| cursor.ConsumeVAddr(ps); |
| continue; |
| } |
| |
| volatile pt_entry_t* next_table = get_next_table_from_entry(pt_val); |
| paddr_t ptable_phys = X86_VIRT_TO_PHYS(next_table); |
| bool lower_unmapped; |
| bool unmap_page_table = false; |
| // Remember where we are unmapping from in case we need to do a second pass to remove a PT. |
| const vaddr_t unmap_vaddr = cursor.vaddr(); |
| // We should recurse and HarvestMappings at the next level if: |
| // 1. This page table entry is in the PML4 of a shared or restricted page table. We must |
| // always recurse in this case because entries in these page tables may have been accessed |
| // via an associated unified page table, which in turn would not set the accessed bits on |
| // the corresponding PML4 entries in this table. |
| // 2. The page table entry has been accessed. We unset the AF later should we end up not |
| // unmapping the page table. |
| bool should_recurse = always_recurse || (pt_val & X86_MMU_PG_A); |
| if (should_recurse) { |
| lower_unmapped = HarvestMapping(next_table, non_terminal_action, terminal_action, |
| lower_level(level), cursor, cm); |
| } else if (non_terminal_action == NonTerminalAction::FreeUnaccessed) { |
| auto status = |
| RemoveMapping(next_table, lower_level(level), EnlargeOperation::No, cursor, cm); |
| // Although we pass in EnlargeOperation::No, the unmap should never fail since we are |
| // unmapping an entire block and never a sub part of a page. |
| ASSERT(status.is_ok()); |
| lower_unmapped = status.value(); |
| const vaddr_t unmap_size = cursor.vaddr() - unmap_vaddr; |
| // If we processed the entire next level then we can ignore lower_unmapped and just directly |
| // assume that the whole page table is empty/unaccessed and that we can unmap it. |
| unmap_page_table = page_aligned(level, unmap_vaddr) && unmap_size >= ps; |
| } else { |
| // No accessed flag and no request to unmap means we are done with this entry. |
| cursor.SkipEntry(ps); |
| continue; |
| } |
| |
| // If the lower page table was accessed and there is uncertainty around whether it might now |
| // be empty, then we have to just scan it and see. |
| if (!unmap_page_table && lower_unmapped) { |
| unmap_page_table = page_table_is_clear(next_table); |
| } |
| // If the top level page is shared, we cannot unmap it here as other page tables may be |
| // referencing its entries. |
| if (IsShared() && level == PageTableLevel::PML4_L) { |
| unmap_page_table = false; |
| } |
| if (unmap_page_table) { |
| vm_page_t* page = paddr_to_vm_page(ptable_phys); |
| if (level == PageTableLevel::PML4_L && IsRestricted() && referenced_pt_ != nullptr) { |
| Guard<Mutex> a{AssertOrderedLock, &referenced_pt_->lock_, referenced_pt_->LockOrder()}; |
| pt_entry_t* referenced_entry = (pt_entry_t*)referenced_pt_->virt() + index; |
| DEBUG_ASSERT(check_equal_ignore_flags(*referenced_entry, *e)); |
| |
| ConsistencyManager cm_referenced(referenced_pt_); |
| referenced_pt_->UnmapEntry(&cm_referenced, level, unmap_vaddr, referenced_entry, false); |
| cm_referenced.Finish(); |
| } |
| UnmapEntry(cm, level, unmap_vaddr, e, /*was_terminal=*/false); |
| |
| DEBUG_ASSERT(page); |
| DEBUG_ASSERT_MSG(page->state() == vm_page_state::MMU, |
| "page %p state %u, paddr %#" PRIxPTR "\n", page, |
| static_cast<uint32_t>(page->state()), X86_VIRT_TO_PHYS(next_table)); |
| DEBUG_ASSERT(!list_in_list(&page->queue_node)); |
| |
| cm->queue_free(page); |
| unmapped = true; |
| } else if ((pt_val & X86_MMU_PG_A) && non_terminal_action != NonTerminalAction::Retain) { |
| // Since we didn't unmap, we need to unset the accessed flag. |
| const IntermediatePtFlags flags = static_cast<T*>(this)->intermediate_flags(); |
| UpdateEntry(cm, level, unmap_vaddr, e, ptable_phys, flags, /*was_terminal=*/false, |
| /*exact_flags=*/true); |
| // For the accessed flag to reliably reset we need to ensure that any leaf pages from here |
| // are not in the TLB so that a re-walk occurs. To avoid having to find every leaf page, |
| // which will probably exceed the consistency managers into count anyway, force trigger a |
| // full shootdown. |
| cm->SetFullShootdown(); |
| } |
| DEBUG_ASSERT(cursor.size() == 0 || page_aligned(level, cursor.vaddr())); |
| } |
| return unmapped; |
| } |
| // Base case of HarvestMapping for smallest page size. |
| void HarvestMappingL0(volatile pt_entry_t* table, TerminalAction terminal_action, |
| MappingCursor& cursor, ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(cursor.size())); |
| |
| uint index = vaddr_to_index(PageTableLevel::PT_L, cursor.vaddr()); |
| for (; index != NO_OF_PT_ENTRIES && cursor.size() != 0; ++index) { |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| if (IS_PAGE_PRESENT(pt_val) && (pt_val & X86_MMU_PG_A)) { |
| const paddr_t paddr = paddr_from_pte(PageTableLevel::PT_L, pt_val); |
| const uint mmu_flags = |
| static_cast<T*>(this)->pt_flags_to_mmu_flags(pt_val, PageTableLevel::PT_L); |
| const PtFlags term_flags = |
| static_cast<T*>(this)->terminal_flags(PageTableLevel::PT_L, mmu_flags); |
| |
| vm_page_t* page = paddr_to_vm_page(paddr); |
| // Mappings for physical VMOs do not have pages associated with them and so there's no state |
| // to update on an access. As the hardware will update any higher level accessed bits for us |
| // we do not even ned to remove the accessed bit in that case. |
| if (likely(page)) { |
| pmm_page_queues()->MarkAccessedDeferredCount(page); |
| |
| if (terminal_action == TerminalAction::UpdateAgeAndHarvest) { |
| UpdateEntry(cm, PageTableLevel::PT_L, cursor.vaddr(), e, |
| paddr_from_pte(PageTableLevel::PT_L, pt_val), term_flags, |
| /*was_terminal=*/true, /*exact_flags=*/true); |
| } |
| } |
| } |
| |
| cursor.ConsumeVAddr(PAGE_SIZE); |
| } |
| DEBUG_ASSERT(cursor.size() == 0 || page_aligned(PageTableLevel::PT_L, cursor.vaddr())); |
| } |
| |
| /** |
| * @brief Walk the page table structures returning the entry and level that maps the address. |
| * |
| * @param table The top-level paging structure's virtual address |
| * @param vaddr The virtual address to retrieve the mapping for |
| * @param ret_level The level of the table that defines the found mapping |
| * @param mapping The mapping that was found |
| * |
| * @return ZX_OK if mapping is found |
| * @return ZX_ERR_NOT_FOUND if mapping is not found |
| */ |
| zx_status_t GetMapping(volatile pt_entry_t* table, vaddr_t vaddr, PageTableLevel level, |
| PageTableLevel* ret_level, volatile pt_entry_t** mapping) TA_REQ(lock_) { |
| DEBUG_ASSERT(table); |
| DEBUG_ASSERT(ret_level); |
| DEBUG_ASSERT(mapping); |
| |
| if (level == PageTableLevel::PT_L) { |
| return GetMappingL0(table, vaddr, ret_level, mapping); |
| } |
| |
| uint index = vaddr_to_index(level, vaddr); |
| volatile pt_entry_t* e = table + index; |
| pt_entry_t pt_val = *e; |
| if (!IS_PAGE_PRESENT(pt_val)) |
| return ZX_ERR_NOT_FOUND; |
| |
| /* if this is a large page, stop here */ |
| if (IS_LARGE_PAGE(pt_val)) { |
| *mapping = e; |
| *ret_level = level; |
| return ZX_OK; |
| } |
| |
| volatile pt_entry_t* next_table = get_next_table_from_entry(pt_val); |
| return GetMapping(next_table, vaddr, lower_level(level), ret_level, mapping); |
| } |
| zx_status_t GetMappingL0(volatile pt_entry_t* table, vaddr_t vaddr, |
| enum PageTableLevel* ret_level, volatile pt_entry_t** mapping) |
| TA_REQ(lock_) { |
| /* do the final page table lookup */ |
| uint index = vaddr_to_index(PageTableLevel::PT_L, vaddr); |
| volatile pt_entry_t* e = table + index; |
| if (!IS_PAGE_PRESENT(*e)) |
| return ZX_ERR_NOT_FOUND; |
| |
| *mapping = e; |
| *ret_level = PageTableLevel::PT_L; |
| return ZX_OK; |
| } |
| |
| // Split the given large page into smaller pages |
| zx_status_t SplitLargePage(PageTableLevel level, vaddr_t vaddr, volatile pt_entry_t* pte, |
| ConsistencyManager* cm) TA_REQ(lock_) { |
| DEBUG_ASSERT_MSG(level != PageTableLevel::PT_L, "tried splitting PT_L"); |
| |
| DEBUG_ASSERT(IS_PAGE_PRESENT(*pte) && IS_LARGE_PAGE(*pte)); |
| volatile pt_entry_t* m = AllocatePageTable(); |
| if (m == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| paddr_t paddr_base = paddr_from_pte(level, *pte); |
| PtFlags flags = static_cast<T*>(this)->split_flags(level, *pte & X86_LARGE_FLAGS_MASK); |
| |
| DEBUG_ASSERT(page_aligned(level, vaddr)); |
| vaddr_t new_vaddr = vaddr; |
| paddr_t new_paddr = paddr_base; |
| size_t ps = page_size(lower_level(level)); |
| for (int i = 0; i < NO_OF_PT_ENTRIES; i++) { |
| volatile pt_entry_t* e = m + i; |
| // If this is a PDP_L (i.e. huge page), flags will include the |
| // PS bit still, so the new PD entries will be large pages. |
| UpdateEntry(cm, lower_level(level), new_vaddr, e, new_paddr, flags, /*was_terminal=*/false); |
| new_vaddr += ps; |
| new_paddr += ps; |
| } |
| DEBUG_ASSERT(new_vaddr == vaddr + page_size(level)); |
| |
| flags = static_cast<T*>(this)->intermediate_flags(); |
| UpdateEntry(cm, level, vaddr, pte, X86_VIRT_TO_PHYS(m), flags, /*was_terminal=*/true); |
| pages_++; |
| return ZX_OK; |
| } |
| |
| void UpdateEntry(ConsistencyManager* cm, PageTableLevel level, vaddr_t vaddr, |
| volatile pt_entry_t* pte, paddr_t paddr, PtFlags flags, bool was_terminal, |
| bool exact_flags = false) TA_REQ(lock_) { |
| DEBUG_ASSERT(pte); |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(paddr)); |
| |
| pt_entry_t olde = *pte; |
| pt_entry_t newe = paddr | flags | X86_MMU_PG_P; |
| |
| // Check if we are actually changing anything, ignoring the accessed and dirty bits unless |
| // exact_flags has been requested to allow for those bits to be explicitly unset. |
| if ((olde & ~(exact_flags ? 0 : (X86_MMU_PG_A | X86_MMU_PG_D))) == newe) { |
| return; |
| } |
| |
| if (level == PageTableLevel::PML4_L && IsShared()) { |
| // If this is a shared page table, the only possible modification should be removal of |
| // the accessed flag. |
| DEBUG_ASSERT(olde == (newe | X86_MMU_PG_A)); |
| } |
| /* set the new entry */ |
| *pte = newe; |
| cm->cache_line_flusher()->FlushPtEntry(pte); |
| |
| /* attempt to invalidate the page */ |
| if (IS_PAGE_PRESENT(olde)) { |
| cm->pending_tlb()->enqueue(vaddr, level, /*is_global_page=*/olde & X86_MMU_PG_G, |
| was_terminal); |
| } |
| } |
| void UnmapEntry(ConsistencyManager* cm, PageTableLevel level, vaddr_t vaddr, |
| volatile pt_entry_t* pte, bool was_terminal) TA_REQ(lock_) { |
| DEBUG_ASSERT(pte); |
| if (level == PageTableLevel::PML4_L) { |
| DEBUG_ASSERT(!IsShared()); |
| } |
| |
| pt_entry_t olde = *pte; |
| |
| *pte = 0; |
| cm->cache_line_flusher()->FlushPtEntry(pte); |
| |
| /* attempt to invalidate the page */ |
| if (IS_PAGE_PRESENT(olde)) { |
| cm->pending_tlb()->enqueue(vaddr, level, /*is_global_page=*/olde & X86_MMU_PG_G, |
| was_terminal); |
| } |
| } |
| |
| // Allocating a new page table |
| // Release the resources associated with this page table. |base| and |size| |
| // are only used for debug checks that the page tables have no more mappings. |
| void DestroyIndividual(vaddr_t base, size_t size) { |
| DEBUG_ASSERT(!IsUnified()); |
| |
| // This lock should be uncontended since Destroy is not supposed to be called in parallel with |
| // any other operation, but hold it anyway so we can clear virt_ and attempt to surface any |
| // bugs. |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| DEBUG_ASSERT(num_references_ == 0); |
| |
| // If this page table has a shared top level page, we need to manually clean up the entries we |
| // created in InitShared. We know for sure that these entries are no longer referenced by |
| // other page tables because we expect those page tables to have been destroyed before this one. |
| if (IsShared()) { |
| DEBUG_ASSERT(virt_ != nullptr); |
| |
| PageTableLevel top = static_cast<T*>(this)->top_level(); |
| pt_entry_t* table = static_cast<pt_entry_t*>(virt_); |
| const uint start = vaddr_to_index(top, base); |
| uint end = vaddr_to_index(top, base + size - 1); |
| // Check the end if it fills out the table entry. |
| if (page_aligned(top, base + size)) { |
| end += 1; |
| } |
| for (uint i = start; i < end; i++) { |
| if (IS_PAGE_PRESENT(table[i])) { |
| volatile pt_entry_t* next_table = get_next_table_from_entry(table[i]); |
| paddr_t ptable_phys = X86_VIRT_TO_PHYS(next_table); |
| vm_page_t* page = paddr_to_vm_page(ptable_phys); |
| pmm_free_page(page); |
| table[i] = 0; |
| } |
| } |
| } |
| |
| if constexpr (DEBUG_ASSERT_IMPLEMENTED) { |
| PageTableLevel top = static_cast<T*>(this)->top_level(); |
| if (virt_) { |
| pt_entry_t* table = static_cast<pt_entry_t*>(virt_); |
| const uint start = vaddr_to_index(top, base); |
| uint end = vaddr_to_index(top, base + size - 1); |
| |
| // Check the end if it fills out the table entry. |
| if (page_aligned(top, base + size)) { |
| end += 1; |
| } |
| |
| for (uint i = start; i < end; ++i) { |
| DEBUG_ASSERT_MSG(!IS_PAGE_PRESENT(table[i]), |
| "Destroy() called on page table with entry 0x%" PRIx64 |
| " still present at index %u; aspace size: %zu, is_shared_: %d\n", |
| table[i], i, size, IsShared()); |
| } |
| } |
| } |
| FreeTopLevelPage(); |
| } |
| |
| // Releases the resources exclusively owned by this unified page table, and update the relevant |
| // metadata on the associated restricted and shared page tables. |
| void DestroyUnified() { |
| DEBUG_ASSERT(IsUnified()); |
| |
| X86PageTableImpl<T>* restricted = nullptr; |
| X86PageTableImpl<T>* shared = nullptr; |
| { |
| // This lock should be uncontended since Destroy is not supposed to be called in parallel with |
| // any other operation, but hold it anyway so we can clear virt_ and attempt to surface any |
| // bugs. We limit the scope in which we hold this lock when destroying unified page tables |
| // because holding it prior to acquiring the shared and restricted page table locks would |
| // violate the lock's ordering rules. We do not destroy the unified page table here, as the |
| // restricted page table may still reference it. |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| // We can copy these pointers to local variables and use them outside of this critical section |
| // because they are notionally const for unified page tables. |
| restricted = referenced_pt_; |
| shared = shared_pt_; |
| shared_pt_ = nullptr; |
| referenced_pt_ = nullptr; |
| } |
| { |
| Guard<Mutex> a{AssertOrderedLock, &shared->lock_, shared->LockOrder()}; |
| // The shared page table should be referenced by at least this page table, and could be |
| // referenced by many other unified page tables. |
| DEBUG_ASSERT(shared->num_references_ > 0); |
| shared->num_references_--; |
| } |
| { |
| Guard<Mutex> a{AssertOrderedLock, &restricted->lock_, restricted->LockOrder()}; |
| // The restricted page table can only be referenced by a singular unified page table. |
| DEBUG_ASSERT(restricted->num_references_ == 1); |
| |
| restricted->num_references_--; |
| restricted->referenced_pt_ = nullptr; |
| } |
| |
| Guard<Mutex> a{AssertOrderedLock, &lock_, LockOrder()}; |
| FreeTopLevelPage(); |
| } |
| |
| // Frees the top level page in this page table. |
| void FreeTopLevelPage() TA_REQ(lock_) { |
| if (phys_) { |
| pmm_free_page(paddr_to_vm_page(phys_)); |
| phys_ = 0; |
| } |
| |
| // Clear virt_ to indicate we are now destroyed, and prevent any misuses of the ArchVmAspace API |
| // from performing use-after-free on the PT. |
| virt_ = nullptr; |
| } |
| |
| // Checks that the given page table entries are equal but ignores the accessed and dirty flags. |
| bool check_equal_ignore_flags(pt_entry_t left, pt_entry_t right) { |
| pt_entry_t no_accessed_dirty_mask = ~X86_MMU_PG_A & ~X86_MMU_PG_D; |
| return (left & no_accessed_dirty_mask) == (right & no_accessed_dirty_mask); |
| } |
| |
| // A reference to another page table that shares entries with this one. |
| // If is_restricted_ is set to true, this references the associated unified page table. |
| // If is_unified_ is set to true, this references the associated restricted page table. |
| // If neither is true, this is set to null. |
| X86PageTableImpl<T>* referenced_pt_ TA_GUARDED(lock_) = nullptr; |
| |
| // A reference to a shared page table whose mappings are also present in this page table. This is |
| // only set for unified page tables. |
| X86PageTableImpl<T>* shared_pt_ TA_GUARDED(lock_) = nullptr; |
| }; |
| |
| #endif // ZIRCON_KERNEL_ARCH_X86_PAGE_TABLES_INCLUDE_ARCH_X86_PAGE_TABLES_PAGE_TABLES_H_ |