| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/storage/f2fs/f2fs.h" |
| |
| namespace f2fs { |
| |
| zx_vaddr_t VmoNode::PageIndexToAddress(pgoff_t page_index) { |
| return safemath::CheckMul<zx_vaddr_t>(page_index, kPageSize).ValueOrDie(); |
| } |
| |
| pgoff_t VmoNode::AddressToPageIndex(zx_vaddr_t address) { |
| return safemath::CheckDiv<pgoff_t>(address, kPageSize).ValueOrDie(); |
| } |
| |
| VmoNode::~VmoNode() { |
| ZX_DEBUG_ASSERT(!active_pages_); |
| if (address_) { |
| zx::vmar::root_self()->unmap(address_, PageIndexToAddress(kVmoSize)); |
| } |
| vmo_.reset(); |
| } |
| |
| zx::status<bool> VmoNode::CreateAndLockVmo(pgoff_t offset) { |
| ZX_DEBUG_ASSERT(offset < kVmoSize); |
| zx_vaddr_t vmo_size = PageIndexToAddress(kVmoSize); |
| if (!vmo_.is_valid()) { |
| if (zx_status_t status = vmo_.create(vmo_size, ZX_VMO_DISCARDABLE, &vmo_); status != ZX_OK) { |
| return zx::error(status); |
| } |
| if (zx_status_t status = zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo_, |
| 0, vmo_size, &address_); |
| status != ZX_OK) { |
| vmo_.reset(); |
| return zx::error(status); |
| } |
| } |
| |
| if (!active_pages_) { |
| zx_status_t status = vmo_.op_range(ZX_VMO_OP_TRY_LOCK, 0, vmo_size, nullptr, 0); |
| if (status == ZX_ERR_UNAVAILABLE) { |
| // When kernel has decommitted any Pages in |vmo_|, the corresponding bits in |page_bitmap_| |
| // are cleared too. |
| zx_vmo_lock_state_t lock_state; |
| status = vmo_.op_range(ZX_VMO_OP_LOCK, 0, vmo_size, &lock_state, sizeof(lock_state)); |
| auto discarded_offset = AddressToPageIndex(lock_state.discarded_offset); |
| auto end = offset + AddressToPageIndex(fbl::round_up(lock_state.discarded_size, kPageSize)); |
| for (; discarded_offset < end; ++discarded_offset) { |
| page_bitmap_.set(discarded_offset, false); |
| } |
| } |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| } |
| |
| bool committed = page_bitmap_[offset]; |
| if (!committed) { |
| page_bitmap_.set(offset, true); |
| } |
| ++active_pages_; |
| return zx::ok(committed); |
| } |
| |
| zx_status_t VmoNode::UnlockVmo(pgoff_t offset) { |
| ZX_DEBUG_ASSERT(offset < kVmoSize); |
| if (--active_pages_) { |
| return ZX_OK; |
| } |
| return vmo_.op_range(ZX_VMO_OP_UNLOCK, 0, PageIndexToAddress(kVmoSize), nullptr, 0); |
| } |
| |
| zx::status<zx_vaddr_t> VmoNode::GetAddress(pgoff_t offset) { |
| ZX_DEBUG_ASSERT(offset < kVmoSize); |
| if (!address_ || !vmo_.is_valid()) { |
| return zx::error(ZX_ERR_UNAVAILABLE); |
| } |
| return zx::ok(safemath::CheckAdd<zx_vaddr_t>(address_, PageIndexToAddress(offset)).ValueOrDie()); |
| } |
| |
| zx::status<bool> VmoManager::CreateAndLockVmo(const pgoff_t index) __TA_EXCLUDES(tree_lock_) { |
| std::lock_guard tree_lock(tree_lock_); |
| auto vmo_node_or = GetVmoNodeUnsafe(GetVmoNodeKey(index)); |
| ZX_DEBUG_ASSERT(vmo_node_or.is_ok()); |
| return vmo_node_or.value()->CreateAndLockVmo(GetOffsetInVmoNode(index)); |
| } |
| |
| zx_status_t VmoManager::UnlockVmo(const pgoff_t index, const bool evict) { |
| std::lock_guard tree_lock(tree_lock_); |
| auto vmo_node_or = FindVmoNodeUnsafe(GetVmoNodeKey(index)); |
| if (vmo_node_or.is_ok()) { |
| if (auto status = vmo_node_or.value()->UnlockVmo(GetOffsetInVmoNode(index)); status != ZX_OK) { |
| return status; |
| } |
| if (evict && !vmo_node_or.value()->GetActivePages()) { |
| __UNUSED auto evicted = vmo_tree_.erase(*vmo_node_or.value()); |
| } |
| } |
| return vmo_node_or.status_value(); |
| } |
| |
| zx::status<zx_vaddr_t> VmoManager::GetAddress(pgoff_t index) { |
| fs::SharedLock tree_lock(tree_lock_); |
| auto vmo_node_or = FindVmoNodeUnsafe(GetVmoNodeKey(index)); |
| if (vmo_node_or.is_ok()) { |
| return vmo_node_or.value()->GetAddress(GetOffsetInVmoNode(index)); |
| } |
| return zx::error(vmo_node_or.error_value()); |
| } |
| |
| void VmoManager::Reset(bool shutdown) { |
| std::lock_guard tree_lock(tree_lock_); |
| pgoff_t prev_key = std::numeric_limits<pgoff_t>::max(); |
| while (!vmo_tree_.is_empty()) { |
| if (shutdown) { |
| __UNUSED auto evicted = vmo_tree_.pop_front(); |
| } else { |
| auto key = (prev_key < std::numeric_limits<pgoff_t>::max()) ? prev_key : 0; |
| auto current = vmo_tree_.lower_bound(key); |
| if (current == vmo_tree_.end()) { |
| break; |
| } |
| // Unless the |prev_key| Page is evicted, try the next Page. |
| if (prev_key == current->GetKey()) { |
| ++current; |
| if (current == vmo_tree_.end()) { |
| break; |
| } |
| } |
| |
| prev_key = current->GetKey(); |
| if (!current->GetActivePages()) { |
| __UNUSED auto evicted = vmo_tree_.erase(*current); |
| } |
| } |
| } |
| } |
| |
| zx::status<VmoNode *> VmoManager::FindVmoNodeUnsafe(const pgoff_t index) { |
| if (auto vmo_node = vmo_tree_.find(index); vmo_node != vmo_tree_.end()) { |
| return zx::ok(&(*vmo_node)); |
| } |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| zx::status<VmoNode *> VmoManager::GetVmoNodeUnsafe(const pgoff_t index) { |
| VmoNode *vmo_node = nullptr; |
| if (auto vmo_node_or = FindVmoNodeUnsafe(index); vmo_node_or.is_error()) { |
| auto new_node = std::make_unique<VmoNode>(index); |
| vmo_node = new_node.get(); |
| vmo_tree_.insert(std::move(new_node)); |
| } else { |
| vmo_node = vmo_node_or.value(); |
| } |
| return zx::ok(vmo_node); |
| } |
| |
| pgoff_t VmoManager::GetOffsetInVmoNode(pgoff_t page_index) { return page_index % kVmoSize; } |
| |
| pgoff_t VmoManager::GetVmoNodeKey(pgoff_t page_index) { |
| return page_index - GetOffsetInVmoNode(page_index); |
| } |
| |
| } // namespace f2fs |