blob: dd624fad32d3bacef6a4a5f0b2e4928171af5418 [file] [log] [blame]
// Copyright 2017 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 <type_traits>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <fbl/vector.h>
#include <zircon/assert.h>
#include "fvm-private.h"
#include "vpartition.h"
namespace fvm {
VPartition::VPartition(VPartitionManager* vpm, size_t entry_index, size_t block_op_size)
: PartitionDeviceType(vpm->zxdev()), mgr_(vpm), entry_index_(entry_index) {
memcpy(&info_, &mgr_->Info(), sizeof(block_info_t));
info_.block_count = 0;
}
VPartition::~VPartition() = default;
zx_status_t VPartition::Create(VPartitionManager* vpm, size_t entry_index,
fbl::unique_ptr<VPartition>* out) {
ZX_DEBUG_ASSERT(entry_index != 0);
auto vp = std::make_unique<VPartition>(vpm, entry_index, vpm->BlockOpSize());
*out = std::move(vp);
return ZX_OK;
}
bool VPartition::SliceGetLocked(uint64_t vslice, uint64_t* out_pslice) const {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
auto extent = --slice_map_.upper_bound(vslice);
if (!extent.IsValid()) {
return false;
}
ZX_DEBUG_ASSERT(extent->start() <= vslice);
return extent->find(vslice, out_pslice);
}
zx_status_t VPartition::CheckSlices(uint64_t vslice_start, size_t* count, bool* allocated) {
fbl::AutoLock lock(&lock_);
if (vslice_start >= mgr_->VSliceMax()) {
return ZX_ERR_OUT_OF_RANGE;
}
if (IsKilledLocked()) {
return ZX_ERR_BAD_STATE;
}
*count = 0;
*allocated = false;
auto extent = --slice_map_.upper_bound(vslice_start);
if (extent.IsValid()) {
ZX_DEBUG_ASSERT(extent->start() <= vslice_start);
if (extent->start() + extent->size() > vslice_start) {
*count = extent->size() - (vslice_start - extent->start());
*allocated = true;
}
}
if (!(*allocated)) {
auto extent = slice_map_.upper_bound(vslice_start);
if (extent.IsValid()) {
ZX_DEBUG_ASSERT(extent->start() > vslice_start);
*count = extent->start() - vslice_start;
} else {
*count = mgr_->VSliceMax() - vslice_start;
}
}
return ZX_OK;
}
void VPartition::SliceSetLocked(uint64_t vslice, uint64_t pslice) {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
auto extent = --slice_map_.upper_bound(vslice);
ZX_DEBUG_ASSERT(!extent.IsValid() || !extent->contains(vslice));
if (extent.IsValid() && (vslice == extent->end())) {
// Easy case: append to existing slice
extent->push_back(pslice);
} else {
// Longer case: there is no extent for this vslice, so we should make
// one.
fbl::unique_ptr<SliceExtent> new_extent(new SliceExtent(vslice));
new_extent->push_back(pslice);
slice_map_.insert(std::move(new_extent));
extent = --slice_map_.upper_bound(vslice);
}
ZX_DEBUG_ASSERT(([this, vslice, pslice]() TA_NO_THREAD_SAFETY_ANALYSIS {
uint64_t mapped_pslice;
return SliceGetLocked(vslice, &mapped_pslice) && mapped_pslice == pslice;
}()));
AddBlocksLocked((mgr_->SliceSize() / info_.block_size));
// Merge with the next contiguous extent (if any)
auto next_extent = slice_map_.upper_bound(vslice);
if (next_extent.IsValid() && (vslice + 1 == next_extent->start())) {
extent->Merge(*next_extent);
slice_map_.erase(*next_extent);
}
}
void VPartition::SliceFreeLocked(uint64_t vslice) {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
ZX_DEBUG_ASSERT(SliceCanFree(vslice));
auto extent = --slice_map_.upper_bound(vslice);
if (vslice != extent->end() - 1) {
// Removing from the middle of an extent; this splits the extent in
// two.
auto new_extent = extent->Split(vslice);
slice_map_.insert(std::move(new_extent));
}
// Removing from end of extent
extent->pop_back();
if (extent->empty()) {
slice_map_.erase(*extent);
}
AddBlocksLocked(-(mgr_->SliceSize() / info_.block_size));
}
void VPartition::ExtentDestroyLocked(uint64_t vslice) TA_REQ(lock_) {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
ZX_DEBUG_ASSERT(SliceCanFree(vslice));
auto extent = --slice_map_.upper_bound(vslice);
size_t length = extent->size();
slice_map_.erase(*extent);
AddBlocksLocked(-((length * mgr_->SliceSize()) / info_.block_size));
}
template <typename T>
static zx_status_t RequestBoundCheck(const T& request, uint64_t vslice_max) {
if (request.offset == 0 || request.offset > vslice_max) {
return ZX_ERR_OUT_OF_RANGE;
} else if (request.length > vslice_max) {
return ZX_ERR_OUT_OF_RANGE;
} else if (request.offset + request.length < request.offset ||
request.offset + request.length > vslice_max) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
// Device protocol (VPartition)
zx_status_t VPartition::DdkGetProtocol(uint32_t proto_id, void* out_protocol) {
auto* proto = static_cast<ddk::AnyProtocol*>(out_protocol);
proto->ctx = this;
switch (proto_id) {
case ZX_PROTOCOL_BLOCK_IMPL:
proto->ops = &block_impl_protocol_ops_;
return ZX_OK;
case ZX_PROTOCOL_BLOCK_PARTITION:
proto->ops = &block_partition_protocol_ops_;
return ZX_OK;
case ZX_PROTOCOL_BLOCK_VOLUME:
proto->ops = &block_volume_protocol_ops_;
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
typedef struct multi_txn_state {
multi_txn_state(size_t total, block_op_t* txn, block_impl_queue_callback cb, void* cookie)
: txns_completed(0), txns_total(total), status(ZX_OK), original(txn), completion_cb(cb),
cookie(cookie) {}
fbl::Mutex lock;
size_t txns_completed TA_GUARDED(lock);
size_t txns_total TA_GUARDED(lock);
zx_status_t status TA_GUARDED(lock);
block_op_t* original TA_GUARDED(lock);
block_impl_queue_callback completion_cb TA_GUARDED(lock);
void* cookie TA_GUARDED(lock);
} multi_txn_state_t;
static void multi_txn_completion(void* cookie, zx_status_t status, block_op_t* txn) {
multi_txn_state_t* state = static_cast<multi_txn_state_t*>(cookie);
bool last_txn = false;
{
fbl::AutoLock lock(&state->lock);
state->txns_completed++;
if (state->status == ZX_OK && status != ZX_OK) {
state->status = status;
}
if (state->txns_completed == state->txns_total) {
last_txn = true;
state->completion_cb(state->cookie, state->status, state->original);
}
}
if (last_txn) {
delete state;
}
delete[] txn;
}
void VPartition::BlockImplQueue(block_op_t* txn, block_impl_queue_callback completion_cb,
void* cookie) {
ZX_DEBUG_ASSERT(mgr_->BlockOpSize() > 0);
switch (txn->command & BLOCK_OP_MASK) {
case BLOCK_OP_READ:
case BLOCK_OP_WRITE:
break;
// Pass-through operations
case BLOCK_OP_FLUSH:
mgr_->Queue(txn, completion_cb, cookie);
return;
default:
fprintf(stderr, "[FVM BlockQueue] Unsupported Command: %x\n", txn->command);
completion_cb(cookie, ZX_ERR_NOT_SUPPORTED, txn);
return;
}
const uint64_t device_capacity = DdkGetSize() / BlockSize();
if (txn->rw.length == 0) {
completion_cb(cookie, ZX_ERR_INVALID_ARGS, txn);
return;
} else if ((txn->rw.offset_dev >= device_capacity) ||
(device_capacity - txn->rw.offset_dev < txn->rw.length)) {
completion_cb(cookie, ZX_ERR_OUT_OF_RANGE, txn);
return;
}
const FormatInfo& format_info = mgr_->format_info();
const uint64_t slice_size = mgr_->SliceSize();
const uint64_t blocks_per_slice = slice_size / BlockSize();
// Start, end both inclusive
uint64_t vslice_start = txn->rw.offset_dev / blocks_per_slice;
uint64_t vslice_end = (txn->rw.offset_dev + txn->rw.length - 1) / blocks_per_slice;
fbl::AutoLock lock(&lock_);
if (vslice_start == vslice_end) {
// Common case: txn occurs within one slice
uint64_t pslice;
if (!SliceGetLocked(vslice_start, &pslice)) {
completion_cb(cookie, ZX_ERR_OUT_OF_RANGE, txn);
return;
}
txn->rw.offset_dev = format_info.GetSliceStart(pslice) / BlockSize() +
(txn->rw.offset_dev % blocks_per_slice);
mgr_->Queue(txn, completion_cb, cookie);
return;
}
// Less common case: txn spans multiple slices
// First, check that all slices are allocated.
// If any are missing, then this txn will fail.
bool contiguous = true;
for (size_t vslice = vslice_start; vslice <= vslice_end; vslice++) {
uint64_t pslice;
if (!SliceGetLocked(vslice, &pslice)) {
completion_cb(cookie, ZX_ERR_OUT_OF_RANGE, txn);
return;
}
uint64_t prev_pslice;
if (vslice != vslice_start && SliceGetLocked(vslice - 1, &prev_pslice) &&
prev_pslice + 1 != pslice) {
contiguous = false;
}
}
// Ideal case: slices are contiguous
if (contiguous) {
uint64_t pslice;
SliceGetLocked(vslice_start, &pslice);
txn->rw.offset_dev = format_info.GetSliceStart(pslice) / BlockSize() +
(txn->rw.offset_dev % blocks_per_slice);
mgr_->Queue(txn, completion_cb, cookie);
return;
}
// Harder case: Noncontiguous slices
const uint64_t txn_count = vslice_end - vslice_start + 1;
fbl::Vector<block_op_t*> txns;
txns.reserve(txn_count);
fbl::unique_ptr<multi_txn_state_t> state(
new multi_txn_state_t(txn_count, txn, completion_cb, cookie));
uint32_t length_remaining = txn->rw.length;
for (size_t i = 0; i < txn_count; i++) {
uint64_t vslice = vslice_start + i;
uint64_t pslice;
SliceGetLocked(vslice, &pslice);
uint64_t offset_vmo = txn->rw.offset_vmo;
uint64_t length;
if (vslice == vslice_start) {
length = fbl::round_up(txn->rw.offset_dev + 1, blocks_per_slice) - txn->rw.offset_dev;
} else if (vslice == vslice_end) {
length = length_remaining;
offset_vmo += txn->rw.length - length_remaining;
} else {
length = blocks_per_slice;
offset_vmo += txns[0]->rw.length + blocks_per_slice * (i - 1);
}
ZX_DEBUG_ASSERT(length <= blocks_per_slice);
ZX_DEBUG_ASSERT(length <= length_remaining);
txns.push_back(reinterpret_cast<block_op_t*>(new uint8_t[mgr_->BlockOpSize()]));
memcpy(txns[i], txn, sizeof(*txn));
txns[i]->rw.offset_vmo = offset_vmo;
txns[i]->rw.length = static_cast<uint32_t>(length);
txns[i]->rw.offset_dev = format_info.GetSliceStart(pslice) / BlockSize();
if (vslice == vslice_start) {
txns[i]->rw.offset_dev += (txn->rw.offset_dev % blocks_per_slice);
}
length_remaining -= txns[i]->rw.length;
}
ZX_DEBUG_ASSERT(length_remaining == 0);
for (size_t i = 0; i < txn_count; i++) {
mgr_->Queue(txns[i], multi_txn_completion, state.get());
}
// TODO(johngro): ask smklein why it is OK to release this managed pointer.
__UNUSED auto ptr = state.release();
}
void VPartition::BlockImplQuery(block_info_t* info_out, size_t* block_op_size_out) {
static_assert(std::is_same<decltype(info_out), decltype(&info_)>::value, "Info type mismatch");
memcpy(info_out, &info_, sizeof(info_));
*block_op_size_out = mgr_->BlockOpSize();
}
static_assert(fvm::kGuidSize == GUID_LENGTH, "Invalid GUID length");
zx_status_t VPartition::BlockPartitionGetGuid(guidtype_t guid_type, guid_t* out_guid) {
fbl::AutoLock lock(&lock_);
if (IsKilledLocked()) {
return ZX_ERR_BAD_STATE;
}
switch (guid_type) {
case GUIDTYPE_TYPE:
memcpy(out_guid, mgr_->GetAllocatedVPartEntry(entry_index_)->type, fvm::kGuidSize);
return ZX_OK;
case GUIDTYPE_INSTANCE:
memcpy(out_guid, mgr_->GetAllocatedVPartEntry(entry_index_)->guid, fvm::kGuidSize);
return ZX_OK;
default:
return ZX_ERR_INVALID_ARGS;
}
}
static_assert(fvm::kMaxVPartitionNameLength < MAX_PARTITION_NAME_LENGTH, "Name Length mismatch");
zx_status_t VPartition::BlockPartitionGetName(char* out_name, size_t capacity) {
if (capacity < fvm::kMaxVPartitionNameLength + 1) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
fbl::AutoLock lock(&lock_);
if (IsKilledLocked()) {
return ZX_ERR_BAD_STATE;
}
memcpy(out_name, mgr_->GetAllocatedVPartEntry(entry_index_)->name, fvm::kMaxVPartitionNameLength);
out_name[fvm::kMaxVPartitionNameLength] = 0;
return ZX_OK;
}
zx_status_t VPartition::BlockVolumeExtend(const slice_extent_t* extent) {
zx_status_t status = RequestBoundCheck(*extent, mgr_->VSliceMax());
if (status != ZX_OK) {
return status;
}
if (extent->length == 0) {
return ZX_OK;
}
return mgr_->AllocateSlices(this, extent->offset, extent->length);
}
zx_status_t VPartition::BlockVolumeShrink(const slice_extent_t* extent) {
zx_status_t status = RequestBoundCheck(*extent, mgr_->VSliceMax());
if (status != ZX_OK) {
return status;
}
if (extent->length == 0) {
return ZX_OK;
}
return mgr_->FreeSlices(this, extent->offset, extent->length);
}
zx_status_t VPartition::BlockVolumeQuery(parent_volume_info_t* out_info) {
// TODO(smklein): Ensure Banjo (parent_volume_info_t) and FIDL (volume_info_t)
// are aligned.
static_assert(sizeof(parent_volume_info_t) == sizeof(volume_info_t), "Info Mismatch");
volume_info_t* info = reinterpret_cast<volume_info_t*>(out_info);
mgr_->Query(info);
return ZX_OK;
}
zx_status_t VPartition::BlockVolumeQuerySlices(const uint64_t* start_list, size_t start_count,
slice_region_t* out_responses_list,
size_t responses_count,
size_t* out_responses_actual) {
if ((start_count > MAX_SLICE_QUERY_REQUESTS) || (start_count > responses_count)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
for (size_t i = 0; i < start_count; i++) {
zx_status_t status;
if ((status = CheckSlices(start_list[i], &out_responses_list[i].count,
&out_responses_list[i].allocated)) != ZX_OK) {
return status;
}
}
*out_responses_actual = start_count;
return ZX_OK;
}
zx_status_t VPartition::BlockVolumeDestroy() {
return mgr_->FreeSlices(this, 0, mgr_->VSliceMax());
}
zx_off_t VPartition::DdkGetSize() {
const zx_off_t sz = mgr_->VSliceMax() * mgr_->SliceSize();
// Check for overflow; enforced when loading driver
ZX_DEBUG_ASSERT(sz / mgr_->VSliceMax() == mgr_->SliceSize());
return sz;
}
void VPartition::DdkUnbind() {
DdkRemove();
}
void VPartition::DdkRelease() {
delete this;
}
zx_device_t* VPartition::GetParent() const {
return mgr_->parent();
}
} // namespace fvm