// Copyright 2016 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 #include #include #include #include #include #include #include #include "vm_priv.h" #define LOCAL_TRACE MAX(VM_GLOBAL_TRACE, 0) VmPageListNode::VmPageListNode(uint64_t offset) : obj_offset_(offset) { LTRACEF("%p offset %#" PRIx64 "\n", this, obj_offset_); } VmPageListNode::~VmPageListNode() { LTRACEF("%p offset %#" PRIx64 "\n", this, obj_offset_); canary_.Assert(); for (__UNUSED auto p : pages_) { DEBUG_ASSERT(p == nullptr); } } vm_page* VmPageListNode::GetPage(size_t index) { canary_.Assert(); DEBUG_ASSERT(index < kPageFanOut); return pages_[index]; } vm_page* VmPageListNode::RemovePage(size_t index) { canary_.Assert(); DEBUG_ASSERT(index < kPageFanOut); auto p = pages_[index]; if (!p) { return nullptr; } pages_[index] = nullptr; return p; } zx_status_t VmPageListNode::AddPage(vm_page* p, size_t index) { canary_.Assert(); DEBUG_ASSERT(index < kPageFanOut); if (pages_[index]) { return ZX_ERR_ALREADY_EXISTS; } pages_[index] = p; return ZX_OK; } VmPageList::VmPageList() { LTRACEF("%p\n", this); } VmPageList::~VmPageList() { LTRACEF("%p\n", this); DEBUG_ASSERT(list_.is_empty()); } zx_status_t VmPageList::AddPage(vm_page* p, uint64_t offset) { uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut); size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut; LTRACEF_LEVEL(2, "%p page %p, offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, p, offset, node_offset, index); // lookup the tree node that holds this page auto pln = list_.find(node_offset); if (!pln.IsValid()) { fbl::AllocChecker ac; fbl::unique_ptr pl = fbl::unique_ptr(new (&ac) VmPageListNode(node_offset)); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } LTRACEF("allocating new inner node %p\n", pl.get()); __UNUSED auto status = pl->AddPage(p, index); DEBUG_ASSERT(status == ZX_OK); list_.insert(fbl::move(pl)); } else { pln->AddPage(p, index); } return ZX_OK; } vm_page* VmPageList::GetPage(uint64_t offset) { uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut); size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut; LTRACEF_LEVEL(2, "%p offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, offset, node_offset, index); // lookup the tree node that holds this page auto pln = list_.find(node_offset); if (!pln.IsValid()) { return nullptr; } return pln->GetPage(index); } zx_status_t VmPageList::FreePage(uint64_t offset) { uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut); size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut; LTRACEF_LEVEL(2, "%p offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, offset, node_offset, index); // lookup the tree node that holds this page auto pln = list_.find(node_offset); if (!pln.IsValid()) { return ZX_ERR_NOT_FOUND; } // free this page auto page = pln->RemovePage(index); if (page) { // if it was the last page in the node, remove the node from the tree if (pln->IsEmpty()) { LTRACEF_LEVEL(2, "%p freeing the list node\n", this); list_.erase(*pln); } pmm_free_page(page); } return ZX_OK; } size_t VmPageList::FreeAllPages() { LTRACEF("%p\n", this); list_node list; list_initialize(&list); size_t count = 0; // per page get a reference to the page pointer inside the page list node auto per_page_func = [&](vm_page*& p, uint64_t offset) { // add the page to our list and null out the inner node list_add_tail(&list, &p->queue_node); p = nullptr; count++; return ZX_ERR_NEXT; }; // walk the tree in order, freeing all the pages on every node ForEveryPage(per_page_func); // return all the pages to the pmm at once pmm_free(&list); // empty the tree list_.clear(); return count; } bool VmPageList::IsEmpty() { return list_.is_empty(); }