| // 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 |
| #ifndef ZIRCON_KERNEL_VM_PMM_NODE_H_ |
| #define ZIRCON_KERNEL_VM_PMM_NODE_H_ |
| |
| #include <fbl/canary.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <kernel/event.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/mutex.h> |
| #include <vm/pmm.h> |
| #include <vm/pmm_checker.h> |
| |
| #include "pmm_arena.h" |
| |
| // per numa node collection of pmm arenas and worker threads |
| class PmmNode { |
| public: |
| PmmNode(); |
| ~PmmNode(); |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(PmmNode); |
| |
| paddr_t PageToPaddr(const vm_page_t* page) TA_NO_THREAD_SAFETY_ANALYSIS; |
| vm_page_t* PaddrToPage(paddr_t addr) TA_NO_THREAD_SAFETY_ANALYSIS; |
| |
| // main allocator routines |
| zx_status_t AllocPage(uint alloc_flags, vm_page_t** page, paddr_t* pa); |
| zx_status_t AllocPages(size_t count, uint alloc_flags, list_node* list); |
| zx_status_t AllocRange(paddr_t address, size_t count, list_node* list); |
| zx_status_t AllocContiguous(size_t count, uint alloc_flags, uint8_t alignment_log2, paddr_t* pa, |
| list_node* list); |
| void FreePage(vm_page* page); |
| void FreeList(list_node* list); |
| |
| // delayed allocator routines |
| void AllocPages(uint alloc_flags, page_request_t* req); |
| bool ClearRequest(page_request_t* req); |
| void SwapRequest(page_request_t* old, page_request_t* new_req); |
| |
| zx_status_t InitReclamation(const uint64_t* watermarks, uint8_t watermark_count, |
| uint64_t debounce, void* context, |
| mem_avail_state_updated_callback_t callback); |
| |
| int RequestThreadLoop(); |
| void InitRequestThread(); |
| |
| uint64_t CountFreePages() const; |
| uint64_t CountTotalBytes() const; |
| |
| // printf free and overall state of the internal arenas |
| // NOTE: both functions skip mutexes and can be called inside timer or crash context |
| // though the data they return may be questionable |
| void DumpFree() const TA_NO_THREAD_SAFETY_ANALYSIS; |
| void Dump(bool is_panic) const TA_NO_THREAD_SAFETY_ANALYSIS; |
| |
| void DumpMemAvailState() const; |
| void DebugMemAvailStateCallback(uint8_t mem_state_idx) const; |
| uint64_t DebugNumPagesTillMemState(uint8_t mem_state_idx) const; |
| uint8_t DebugMaxMemAvailState() const; |
| |
| zx_status_t AddArena(const pmm_arena_info_t* info); |
| |
| // Returns the number of arenas. |
| size_t NumArenas() const; |
| |
| // Copies |count| pmm_arena_info_t objects into |buffer| starting with the |i|-th arena ordered by |
| // base address. For example, passing an |i| of 1 would skip the 1st arena. |
| // |
| // The objects will be sorted in ascending order by arena base address. |
| // |
| // Returns ZX_ERR_OUT_OF_RANGE if |count| is 0 or |i| and |count| specificy an invalid range. |
| // |
| // Returns ZX_ERR_BUFFER_TOO_SMALL if the buffer is too small. |
| zx_status_t GetArenaInfo(size_t count, uint64_t i, pmm_arena_info_t* buffer, size_t buffer_size); |
| |
| // add new pages to the free queue. used when boostrapping a PmmArena |
| void AddFreePages(list_node* list); |
| |
| PageQueues* GetPageQueues() { return &page_queues_; } |
| |
| // Fill all free pages with a pattern and arm the checker. See |PmmChecker|. |
| // |
| // This is a no-op if the checker is not enabled. See |EnableFreePageFilling| |
| void FillFreePagesAndArm(); |
| |
| // Synchronously walk the PMM's free list and validate each page. This is an incredibly expensive |
| // operation and should only be used for debugging purposes. |
| void CheckAllFreePages(); |
| |
| #if __has_feature(address_sanitizer) |
| // Synchronously walk the PMM's free list and poison each page. |
| void PoisonAllFreePages(); |
| #endif |
| |
| // Enable the free fill checker with the specified fill size and action, and begin filling freed |
| // pages going forward. See |PmmChecker| for definition of fill size. |
| // |
| // Note, pages freed piror to calling this method will remain unfilled. To fill them, call |
| // |FillFreePagesAndArm|. |
| void EnableFreePageFilling(size_t fill_size, PmmChecker::Action action); |
| |
| // Disarm and disable the free fill checker. |
| void DisableChecker(); |
| |
| // Return a pointer to this object's free fill checker. |
| // |
| // For test and diagnostic purposes. |
| PmmChecker* Checker() { return &checker_; } |
| |
| static int64_t get_alloc_failed_count(); |
| |
| private: |
| void FreePageHelperLocked(vm_page* page) TA_REQ(lock_); |
| void FreeListLocked(list_node* list) TA_REQ(lock_); |
| |
| void ProcessPendingRequests(); |
| |
| void UpdateMemAvailStateLocked() TA_REQ(lock_); |
| void SetMemAvailStateLocked(uint8_t mem_avail_state) TA_REQ(lock_); |
| |
| void IncrementFreeCountLocked(uint64_t amount) TA_REQ(lock_) { |
| free_count_ += amount; |
| |
| if (unlikely(free_count_ >= mem_avail_state_upper_bound_)) { |
| UpdateMemAvailStateLocked(); |
| } |
| } |
| void DecrementFreeCountLocked(uint64_t amount) TA_REQ(lock_) { |
| DEBUG_ASSERT(free_count_ >= amount); |
| free_count_ -= amount; |
| |
| if (unlikely(free_count_ <= mem_avail_state_lower_bound_)) { |
| UpdateMemAvailStateLocked(); |
| } |
| } |
| |
| bool InOomStateLocked() TA_REQ(lock_); |
| |
| void AllocPageHelperLocked(vm_page_t* page) TA_REQ(lock_); |
| |
| void AsanPoisonPage(vm_page_t*, uint8_t) TA_REQ(lock_); |
| |
| void AsanUnpoisonPage(vm_page_t*) TA_REQ(lock_); |
| |
| fbl::Canary<fbl::magic("PNOD")> canary_; |
| |
| mutable DECLARE_MUTEX(PmmNode) lock_; |
| |
| uint64_t arena_cumulative_size_ TA_GUARDED(lock_) = 0; |
| uint64_t free_count_ TA_GUARDED(lock_) = 0; |
| |
| fbl::SizedDoublyLinkedList<PmmArena*> arena_list_ TA_GUARDED(lock_); |
| |
| list_node free_list_ TA_GUARDED(lock_) = LIST_INITIAL_VALUE(free_list_); |
| |
| // List of pending requests. |
| list_node_t request_list_ TA_GUARDED(lock_) = LIST_INITIAL_VALUE(request_list_); |
| // Request currently being processed. This is tracked seperately from request_list_ |
| // because ClearRequest() handles the two cases differently. |
| page_request_t* current_request_ TA_GUARDED(lock_) = nullptr; |
| |
| Event free_pages_evt_; |
| Event request_evt_; |
| |
| uint64_t mem_avail_state_watermarks_[MAX_WATERMARK_COUNT] TA_GUARDED(lock_); |
| uint8_t mem_avail_state_watermark_count_ TA_GUARDED(lock_); |
| uint8_t mem_avail_state_cur_index_ TA_GUARDED(lock_); |
| uint64_t mem_avail_state_debounce_ TA_GUARDED(lock_); |
| uint64_t mem_avail_state_upper_bound_ TA_GUARDED(lock_); |
| uint64_t mem_avail_state_lower_bound_ TA_GUARDED(lock_); |
| void* mem_avail_state_context_ TA_GUARDED(lock_); |
| mem_avail_state_updated_callback_t mem_avail_state_callback_ TA_GUARDED(lock_); |
| |
| Thread* request_thread_ = nullptr; |
| ktl::atomic<bool> request_thread_live_ = true; |
| |
| PageQueues page_queues_; |
| |
| bool free_fill_enabled_ TA_GUARDED(lock_) = false; |
| PmmChecker checker_ TA_GUARDED(lock_); |
| }; |
| |
| // We don't need to hold the arena lock while executing this, since it is |
| // only accesses values that are set once during system initialization. |
| inline vm_page_t* PmmNode::PaddrToPage(paddr_t addr) TA_NO_THREAD_SAFETY_ANALYSIS { |
| for (auto& a : arena_list_) { |
| if (a.address_in_arena(addr)) { |
| size_t index = (addr - a.base()) / PAGE_SIZE; |
| return a.get_page(index); |
| } |
| } |
| return nullptr; |
| } |
| |
| #endif // ZIRCON_KERNEL_VM_PMM_NODE_H_ |