blob: f24cf92b7a157cf96863be4a8394f1b8f6213ec4 [file] [log] [blame]
// Copyright 2018 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 <lib/fit/defer.h>
#include <lib/fzl/pinned-vmo.h>
#include <climits>
#include <limits>
#include <memory>
namespace fzl {
zx_status_t PinnedVmo::Pin(const zx::vmo& vmo, const zx::bti& bti, uint32_t options) {
if (!vmo.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
// To pin the entire VMO, we need to get the length:
zx_status_t res;
uint64_t vmo_size;
res = vmo.get_size(&vmo_size);
if (res != ZX_OK) {
return res;
}
return PinInternal(0, vmo_size, vmo, bti, options);
}
zx_status_t PinnedVmo::PinRange(uint64_t offset, uint64_t len, const zx::vmo& vmo,
const zx::bti& bti, uint32_t options) {
const size_t kPageSize = zx_system_get_page_size();
if ((len & (kPageSize - 1)) || (offset & (kPageSize - 1)) || len == 0) {
return ZX_ERR_INVALID_ARGS;
}
return PinInternal(offset, len, vmo, bti, options);
}
zx_status_t PinnedVmo::PinInternal(uint64_t offset, uint64_t len, const zx::vmo& vmo,
const zx::bti& bti, uint32_t options) {
const size_t kPageSize = zx_system_get_page_size();
zx_status_t res;
// If we are holding a pinned memory token, then we are already holding a
// pinned VMO. It is an error to try and pin a new VMO without first
// explicitly unpinning the old one.
if (pmt_.is_valid()) {
ZX_DEBUG_ASSERT(regions_ != nullptr);
ZX_DEBUG_ASSERT(region_count_ > 0);
return ZX_ERR_BAD_STATE;
}
// Check our args, read/write/bti_contiguous is all that users may ask for.
constexpr uint32_t kAllowedOptions = ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE | ZX_BTI_CONTIGUOUS;
if (((options & kAllowedOptions) != options) || !vmo.is_valid() || !bti.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
// Allocate storage for the results.
ZX_DEBUG_ASSERT((len > 0) && !(len & (kPageSize - 1)));
ZX_DEBUG_ASSERT((len / kPageSize) < std::numeric_limits<uint32_t>::max());
ZX_DEBUG_ASSERT(!(offset & (kPageSize - 1)));
fbl::AllocChecker ac;
uint32_t page_count = static_cast<uint32_t>(len / kPageSize);
if (options & ZX_BTI_CONTIGUOUS) {
page_count = 1;
}
std::unique_ptr<zx_paddr_t[]> addrs(new (&ac) zx_paddr_t[page_count]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Now actually pin the region.
res = bti.pin(options, vmo, offset, len, addrs.get(), page_count, &pmt_);
if (res != ZX_OK) {
return res;
}
// From here on out, if anything goes wrong, we need to make sure to clean
// up. Setup an autocall to take care of this for us.
auto cleanup = fit::defer([&]() { UnpinInternal(); });
if (options & ZX_BTI_CONTIGUOUS) {
// We can do less work in the contiguous case.
regions_.reset(new (&ac) Region[1]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
region_count_ = 1;
regions_[0].phys_addr = addrs[0];
// The region is the size of the entire vmo in the contiguous case.
regions_[0].size = len;
cleanup.cancel();
return ZX_OK;
}
// Do a quick pass over the pages to figure out how many adjacent pages we
// can merge. This will let us know how many regions we will need storage
// for our regions array.
zx_paddr_t last = addrs[0];
region_count_ = 1;
for (uint32_t i = 1; i < page_count; ++i) {
if (addrs[i] != (last + kPageSize)) {
++region_count_;
}
last = addrs[i];
}
// Allocate storage for our regions.
regions_.reset(new (&ac) Region[region_count_]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Finally, go ahead and merge any adjacent pages to compute our set of
// regions and we should be good to go;
regions_[0].phys_addr = addrs[0];
regions_[0].size = kPageSize;
for (uint32_t i = 1, j = 0; i < page_count; ++i) {
ZX_DEBUG_ASSERT(j < region_count_);
if ((regions_[j].phys_addr + regions_[j].size) == addrs[i]) {
// Merge!
regions_[j].size += kPageSize;
} else {
// New Region!
++j;
ZX_DEBUG_ASSERT(j < region_count_);
regions_[j].phys_addr = addrs[i];
regions_[j].size = kPageSize;
}
}
cleanup.cancel();
return ZX_OK;
}
void PinnedVmo::Unpin() {
if (!pmt_.is_valid()) {
ZX_DEBUG_ASSERT(regions_ == nullptr);
ZX_DEBUG_ASSERT(region_count_ == 0);
return;
}
ZX_DEBUG_ASSERT(regions_ != nullptr);
ZX_DEBUG_ASSERT(region_count_ > 0);
UnpinInternal();
}
void PinnedVmo::UnpinInternal() {
ZX_DEBUG_ASSERT(pmt_.is_valid());
// Given the level of sanity checking we have done so far, it should be
// completely impossible for us to fail to unpin this memory.
[[maybe_unused]] zx_status_t res;
res = pmt_.unpin();
ZX_DEBUG_ASSERT(res == ZX_OK);
regions_.reset();
region_count_ = 0;
}
} // namespace fzl