| // 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 |