blob: 1de1a9f25dbbbc330a93ad23f2f9530151c4ce79 [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 <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fs/mapped-vmo.h>
#include <zircon/compiler.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <zircon/thread_annotations.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/limits.h>
#include <fbl/new.h>
#include <threads.h>
#include "fvm-private.h"
namespace fvm {
fbl::unique_ptr<SliceExtent> SliceExtent::Split(size_t vslice) {
ZX_DEBUG_ASSERT(start() <= vslice);
ZX_DEBUG_ASSERT(vslice < end());
fbl::AllocChecker ac;
fbl::unique_ptr<SliceExtent> new_extent(new (&ac) SliceExtent(vslice + 1));
if (!ac.check()) {
return nullptr;
}
new_extent->pslices_.reserve(end() - vslice, &ac);
if (!ac.check()) {
return nullptr;
}
for (size_t vs = vslice + 1; vs < end(); vs++) {
ZX_ASSERT(new_extent->push_back(get(vs)));
}
while (!is_empty() && vslice + 1 != end()) {
pop_back();
}
return fbl::move(new_extent);
}
bool SliceExtent::Merge(const SliceExtent& other) {
ZX_DEBUG_ASSERT(end() == other.start());
fbl::AllocChecker ac;
pslices_.reserve(other.size(), &ac);
if (!ac.check()) {
return false;
}
for (size_t vs = other.start(); vs < other.end(); vs++) {
ZX_ASSERT(push_back(other.get(vs)));
}
return true;
}
VPartitionManager::VPartitionManager(zx_device_t* parent, const block_info_t& info)
: ManagerDeviceType(parent), info_(info), metadata_(nullptr), metadata_size_(0),
slice_size_(0) {}
VPartitionManager::~VPartitionManager() = default;
zx_status_t VPartitionManager::Create(zx_device_t* dev, fbl::unique_ptr<VPartitionManager>* out) {
block_info_t block_info;
size_t actual = 0;
ssize_t rc = device_ioctl(dev, IOCTL_BLOCK_GET_INFO, nullptr, 0, &block_info,
sizeof(block_info), &actual);
if (rc < 0) {
return static_cast<zx_status_t>(rc);
} else if (actual != sizeof(block_info)) {
return ZX_ERR_BAD_STATE;
} else if (block_info.block_size == 0) {
return ZX_ERR_BAD_STATE;
}
fbl::AllocChecker ac;
auto vpm = fbl::make_unique_checked<VPartitionManager>(&ac, dev, block_info);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*out = fbl::move(vpm);
return ZX_OK;
}
zx_status_t VPartitionManager::AddPartition(fbl::unique_ptr<VPartition> vp) const {
auto ename = reinterpret_cast<const char*>(GetAllocatedVPartEntry(vp->GetEntryIndex())->name);
char name[FVM_NAME_LEN + 32];
snprintf(name, sizeof(name), "%.*s-p-%zu", FVM_NAME_LEN, ename, vp->GetEntryIndex());
zx_status_t status;
if ((status = vp->DdkAdd(name)) != ZX_OK) {
return status;
}
vp.release();
return ZX_OK;
}
zx_status_t VPartitionManager::Load() {
fbl::AutoLock lock(&lock_);
auto auto_detach = fbl::MakeAutoCall([&]() TA_NO_THREAD_SAFETY_ANALYSIS {
// "Load" is running in a background thread called by bind.
// This thread will be joined when the fvm_device is released,
// but it must be added to be released.
//
// If we fail to initialize anything before it is added,
// detach the thread and clean up gracefully.
thrd_detach(init_);
// Clang's thread analyzer doesn't think we're holding this lock, but
// we clearly are, and need to release it before deleting the
// VPartitionManager.
lock.release();
delete this;
});
// Read the superblock first, to determine the slice sice
iotxn_t* txn = nullptr;
zx_status_t status = iotxn_alloc(&txn, IOTXN_ALLOC_POOL, FVM_BLOCK_SIZE);
if (status != ZX_OK) {
return status;
}
txn->opcode = IOTXN_OP_READ;
txn->offset = 0;
txn->length = FVM_BLOCK_SIZE;
iotxn_synchronous_op(parent(), txn);
if (txn->status != ZX_OK) {
status = txn->status;
iotxn_release(txn);
return status;
}
fvm_t sb;
iotxn_copyfrom(txn, &sb, sizeof(sb), 0);
iotxn_release(txn);
// Validate the superblock, confirm the slice size
slice_size_ = sb.slice_size;
if (info_.block_size == 0 || SliceSize() % info_.block_size) {
return ZX_ERR_BAD_STATE;
} else if (sb.vpartition_table_size != kVPartTableLength) {
return ZX_ERR_BAD_STATE;
} else if (sb.allocation_table_size != AllocTableLength(DiskSize(), SliceSize())) {
return ZX_ERR_BAD_STATE;
}
metadata_size_ = fvm::MetadataSize(DiskSize(), SliceSize());
// Now that the slice size is known, read the rest of the metadata
size_t dual_metadata_size = 2 * MetadataSize();
fbl::unique_ptr<MappedVmo> mvmo;
status = MappedVmo::Create(dual_metadata_size, "fvm-meta", &mvmo);
if (status != ZX_OK) {
return status;
}
// Read both copies of metadata, ensure at least one is valid
status = iotxn_alloc_vmo(&txn, IOTXN_ALLOC_POOL, mvmo->GetVmo(), 0, dual_metadata_size);
if (status != ZX_OK) {
return status;
}
txn->opcode = IOTXN_OP_READ;
txn->offset = 0;
txn->length = dual_metadata_size;
iotxn_synchronous_op(parent(), txn);
if (txn->status != ZX_OK) {
status = txn->status;
iotxn_release(txn);
return status;
}
iotxn_release(txn);
const void* backup = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(mvmo->GetData()) +
MetadataSize());
const void* metadata;
if ((status = fvm_validate_header(mvmo->GetData(), backup, MetadataSize(),
&metadata)) != ZX_OK) {
return status;
}
// TODO(smklein): Compare the performance of the current reading behavior
// (read into VMO, map into VMAR, cut VMAR in half) with an alternative
// which waits to map the VMO by using zx_vmo_read twice beforehand instead.
first_metadata_is_primary_ = (metadata == mvmo->GetData());
if ((status = mvmo->Shrink(PrimaryOffsetLocked(), MetadataSize())) != ZX_OK) {
return status;
}
// Begin initializing the underlying partitions
metadata_ = fbl::move(mvmo);
if ((status = DdkAdd("fvm")) != ZX_OK) {
return status;
}
auto_detach.cancel();
// 0th vpartition is invalid
fbl::unique_ptr<VPartition> vpartitions[FVM_MAX_ENTRIES] = {};
// Iterate through FVM Entry table, allocating the VPartitions which
// claim to have slices.
for (size_t i = 1; i < FVM_MAX_ENTRIES; i++) {
if (GetVPartEntryLocked(i)->slices == 0) {
continue;
} else if ((status = VPartition::Create(this, i, &vpartitions[i])) != ZX_OK) {
return status;
}
}
// Iterate through the Slice Allocation table, filling the slice maps
// of VPartitions.
for (uint32_t i = 1; i < GetFvmLocked()->pslice_count; i++) {
const slice_entry_t* entry = GetSliceEntryLocked(i);
if (entry->vpart == FVM_SLICE_FREE) {
continue;
}
if (vpartitions[entry->vpart] == nullptr) {
return ZX_ERR_BAD_STATE;
}
// It's fine to load the slices while not holding the vpartition
// lock; no VPartition devices exist yet.
vpartitions[entry->vpart]->SliceSetUnsafe(entry->vslice, i);
}
lock.release();
// Iterate through 'valid' VPartitions, and create their devices.
size_t device_count = 0;
for (size_t i = 0; i < FVM_MAX_ENTRIES; i++) {
if (vpartitions[i] == nullptr) {
continue;
}
if (AddPartition(fbl::move(vpartitions[i]))) {
continue;
}
device_count++;
}
return ZX_OK;
}
zx_status_t VPartitionManager::WriteFvmLocked() {
iotxn_t* txn = nullptr;
zx_status_t status = iotxn_alloc_vmo(&txn, IOTXN_ALLOC_POOL,
metadata_->GetVmo(), 0,
MetadataSize());
if (status != ZX_OK) {
return status;
}
txn->opcode = IOTXN_OP_WRITE;
// If we were reading from the primary, write to the backup.
txn->offset = BackupOffsetLocked();
txn->length = MetadataSize();
GetFvmLocked()->generation++;
fvm_update_hash(GetFvmLocked(), MetadataSize());
iotxn_synchronous_op(parent_, txn);
status = txn->status;
iotxn_release(txn);
if (status != ZX_OK) {
return status;
}
// We only allow the switch of "write to the other copy of metadata"
// once a valid version has been written entirely.
first_metadata_is_primary_ = !first_metadata_is_primary_;
return ZX_OK;
}
zx_status_t VPartitionManager::FindFreeVPartEntryLocked(size_t* out) const {
for (size_t i = 1; i < FVM_MAX_ENTRIES; i++) {
const vpart_entry_t* entry = GetVPartEntryLocked(i);
if (entry->slices == 0) {
*out = i;
return ZX_OK;
}
}
return ZX_ERR_NO_SPACE;
}
zx_status_t VPartitionManager::FindFreeSliceLocked(size_t* out, size_t hint) const {
const size_t maxSlices = UsableSlicesCount(DiskSize(), SliceSize());
hint = fbl::max(hint, 1lu);
for (size_t i = hint; i <= maxSlices; i++) {
if (GetSliceEntryLocked(i)->vpart == 0) {
*out = i;
return ZX_OK;
}
}
for (size_t i = 1; i < hint; i++) {
if (GetSliceEntryLocked(i)->vpart == 0) {
*out = i;
return ZX_OK;
}
}
return ZX_ERR_NO_SPACE;
}
zx_status_t VPartitionManager::AllocateSlices(VPartition* vp, size_t vslice_start,
size_t count) {
fbl::AutoLock lock(&lock_);
return AllocateSlicesLocked(vp, vslice_start, count);
}
zx_status_t VPartitionManager::AllocateSlicesLocked(VPartition* vp, size_t vslice_start,
size_t count) {
if (vslice_start + count > VSliceMax()) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = ZX_OK;
size_t hint = 0;
{
fbl::AutoLock lock(&vp->lock_);
if (vp->IsKilledLocked())
return ZX_ERR_BAD_STATE;
for (size_t i = 0; i < count; i++) {
size_t pslice;
auto vslice = vslice_start + i;
if (vp->SliceGetLocked(vslice) != PSLICE_UNALLOCATED) {
status = ZX_ERR_INVALID_ARGS;
}
if ((status != ZX_OK) ||
((status = FindFreeSliceLocked(&pslice, hint)) != ZX_OK) ||
((status = vp->SliceSetLocked(vslice, static_cast<uint32_t>(pslice)) != ZX_OK))) {
for (int j = static_cast<int>(i - 1); j >= 0; j--) {
vslice = vslice_start + j;
GetSliceEntryLocked(vp->SliceGetLocked(vslice))->vpart = PSLICE_UNALLOCATED;
vp->SliceFreeLocked(vslice);
}
return status;
}
slice_entry_t* alloc_entry = GetSliceEntryLocked(pslice);
auto vpart = vp->GetEntryIndex();
ZX_DEBUG_ASSERT(vpart <= VPART_MAX);
ZX_DEBUG_ASSERT(vslice <= VSLICE_MAX);
alloc_entry->vpart = vpart & VPART_MAX;
alloc_entry->vslice = vslice & VSLICE_MAX;
hint = pslice + 1;
}
}
if ((status = WriteFvmLocked()) != ZX_OK) {
// Undo allocation in the event of failure; avoid holding VPartition
// lock while writing to fvm.
fbl::AutoLock lock(&vp->lock_);
for (int j = static_cast<int>(count - 1); j >= 0; j--) {
auto vslice = vslice_start + j;
GetSliceEntryLocked(vp->SliceGetLocked(vslice))->vpart = PSLICE_UNALLOCATED;
vp->SliceFreeLocked(vslice);
}
}
return status;
}
zx_status_t VPartitionManager::FreeSlices(VPartition* vp, size_t vslice_start,
size_t count) {
fbl::AutoLock lock(&lock_);
return FreeSlicesLocked(vp, vslice_start, count);
}
zx_status_t VPartitionManager::FreeSlicesLocked(VPartition* vp, size_t vslice_start,
size_t count) {
if (vslice_start + count > VSliceMax() || count > VSliceMax()) {
return ZX_ERR_INVALID_ARGS;
}
bool freed_something = false;
{
fbl::AutoLock lock(&vp->lock_);
if (vp->IsKilledLocked())
return ZX_ERR_BAD_STATE;
// Sync first, before removing slices, so iotxns in-flight cannot
// operate on 'unowned' slices.
zx_status_t status;
status = device_ioctl(parent(), IOCTL_DEVICE_SYNC, nullptr, 0, nullptr, 0, nullptr);
if (status != ZX_OK) {
return status;
}
if (vslice_start == 0) {
// Special case: Freeing entire VPartition
for (auto extent = vp->ExtentBegin(); extent.IsValid(); extent = vp->ExtentBegin()) {
while (!extent->is_empty()) {
auto vslice = extent->end() - 1;
GetSliceEntryLocked(vp->SliceGetLocked(vslice))->vpart = PSLICE_UNALLOCATED;
ZX_ASSERT(vp->SliceFreeLocked(vslice));
}
}
// Remove device, VPartition if this was a request to free all slices.
device_remove(mxdev());
auto entry = GetVPartEntryLocked(vp->GetEntryIndex());
entry->clear();
vp->KillLocked();
freed_something = true;
} else {
for (int i = static_cast<int>(count - 1); i >= 0; i--) {
auto vslice = vslice_start + i;
if (vp->SliceCanFree(vslice)) {
size_t pslice = vp->SliceGetLocked(vslice);
if (!freed_something) {
// The first 'free' is the only one which can fail -- it
// has the potential to split extents, which may require
// memory allocation.
if (!vp->SliceFreeLocked(vslice)) {
return ZX_ERR_NO_MEMORY;
}
} else {
ZX_ASSERT(vp->SliceFreeLocked(vslice));
}
GetSliceEntryLocked(pslice)->vpart = 0;
freed_something = true;
}
}
}
}
if (!freed_something) {
return ZX_ERR_INVALID_ARGS;
}
return WriteFvmLocked();
}
// Device protocol (FVM)
zx_status_t VPartitionManager::DdkIoctl(uint32_t op, const void* cmd,
size_t cmdlen, void* reply, size_t max,
size_t* out_actual) {
switch (op) {
case IOCTL_BLOCK_FVM_ALLOC: {
if (cmdlen < sizeof(alloc_req_t))
return ZX_ERR_BUFFER_TOO_SMALL;
const alloc_req_t* request = static_cast<const alloc_req_t*>(cmd);
if (request->slice_count >= fbl::numeric_limits<uint32_t>::max()) {
return ZX_ERR_OUT_OF_RANGE;
} else if (request->slice_count == 0) {
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t status;
fbl::unique_ptr<VPartition> vpart;
{
fbl::AutoLock lock(&lock_);
size_t vpart_entry;
if ((status = FindFreeVPartEntryLocked(&vpart_entry)) != ZX_OK) {
return status;
}
if ((status = VPartition::Create(this, vpart_entry, &vpart)) != ZX_OK) {
return status;
}
auto entry = GetVPartEntryLocked(vpart_entry);
entry->init(request->type, request->guid,
static_cast<uint32_t>(request->slice_count),
request->name);
if ((status = AllocateSlicesLocked(vpart.get(), 0,
request->slice_count)) != ZX_OK) {
entry->slices = 0; // Undo VPartition allocation
return status;
}
}
if ((status = AddPartition(fbl::move(vpart))) != ZX_OK) {
return status;
}
return ZX_OK;
}
case IOCTL_BLOCK_FVM_QUERY: {
if (max < sizeof(fvm_info_t))
return ZX_ERR_BUFFER_TOO_SMALL;
fvm_info_t* info = static_cast<fvm_info_t*>(reply);
info->slice_size = SliceSize();
info->vslice_count = VSliceMax();
*out_actual = sizeof(fvm_info_t);
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_ERR_NOT_SUPPORTED;
}
void VPartitionManager::DdkUnbind() {
device_remove(mxdev());
}
void VPartitionManager::DdkRelease() {
thrd_join(init_, nullptr);
delete this;
}
VPartition::VPartition(VPartitionManager* vpm, size_t entry_index)
: PartitionDeviceType(vpm->mxdev()), 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);
fbl::AllocChecker ac;
auto vp = fbl::make_unique_checked<VPartition>(&ac, vpm, entry_index);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*out = fbl::move(vp);
return ZX_OK;
}
static void vpart_block_complete(iotxn_t* txn, void* cookie) {
block_callbacks_t* cb;
memcpy(&cb, txn->extra, sizeof(void*));
cb->complete(cookie, txn->status);
iotxn_release(txn);
}
uint32_t VPartition::SliceGetLocked(size_t vslice) const {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
auto extent = --slice_map_.upper_bound(vslice);
if (!extent.IsValid()) {
return 0;
}
ZX_DEBUG_ASSERT(extent->start() <= vslice);
return extent->get(vslice);
}
zx_status_t VPartition::SliceSetLocked(size_t vslice, uint32_t pslice) {
ZX_DEBUG_ASSERT(vslice < mgr_->VSliceMax());
auto extent = --slice_map_.upper_bound(vslice);
ZX_DEBUG_ASSERT(!extent.IsValid() || extent->get(vslice) == PSLICE_UNALLOCATED);
if (extent.IsValid() && (vslice == extent->end())) {
// Easy case: append to existing slice
if (!extent->push_back(pslice)) {
return ZX_ERR_NO_MEMORY;
}
ZX_DEBUG_ASSERT(SliceGetLocked(vslice) == pslice);
AddBlocksLocked((mgr_->SliceSize() / info_.block_size));
return ZX_OK;
}
// Longer case: there is no extent for this vslice, so we should make
// one.
fbl::AllocChecker ac;
fbl::unique_ptr<SliceExtent> new_extent(new (&ac) SliceExtent(vslice));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
} else if (!new_extent->push_back(pslice)) {
return ZX_ERR_NO_MEMORY;
}
ZX_DEBUG_ASSERT(new_extent->GetKey() == vslice);
ZX_DEBUG_ASSERT(new_extent->get(vslice) == pslice);
slice_map_.insert(fbl::move(new_extent));
auto nextExtent = --slice_map_.upper_bound(vslice + 1);
if (nextExtent.IsValid() && (vslice + 1 == nextExtent->start())) {
// Try to coalesce with the next slice
extent = --slice_map_.upper_bound(vslice);
if (extent->Merge(*nextExtent)) {
slice_map_.erase(*nextExtent);
}
}
ZX_DEBUG_ASSERT(SliceGetLocked(vslice) == pslice);
AddBlocksLocked((mgr_->SliceSize() / info_.block_size));
return ZX_OK;
}
bool VPartition::SliceFreeLocked(size_t vslice) TA_REQ(lock_) {
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);
if (new_extent == nullptr) {
return false;
}
slice_map_.insert(fbl::move(new_extent));
}
// Removing from end of extent
extent->pop_back();
if (extent->is_empty()) {
slice_map_.erase(*extent);
}
AddBlocksLocked(-(mgr_->SliceSize() / info_.block_size));
return true;
}
void VPartition::Txn(uint32_t opcode, zx_handle_t vmo, uint64_t length,
uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
zx_status_t status;
iotxn_t* txn;
if ((status = iotxn_alloc_vmo(&txn, IOTXN_ALLOC_POOL, vmo, vmo_offset,
length)) != ZX_OK) {
callbacks_->complete(cookie, status);
return;
}
txn->opcode = opcode;
txn->offset = dev_offset;
txn->complete_cb = vpart_block_complete;
txn->cookie = cookie;
memcpy(txn->extra, &callbacks_, sizeof(void*));
iotxn_queue(mxdev(), txn);
}
static zx_status_t RequestBoundCheck(const extend_request_t* request,
size_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::DdkIoctl(uint32_t op, const void* cmd, size_t cmdlen,
void* reply, size_t max, size_t* out_actual) {
switch (op) {
case IOCTL_BLOCK_GET_INFO: {
block_info_t* info = static_cast<block_info_t*>(reply);
if (max < sizeof(*info))
return ZX_ERR_BUFFER_TOO_SMALL;
fbl::AutoLock lock(&lock_);
if (IsKilledLocked())
return ZX_ERR_BAD_STATE;
memcpy(info, &info_, sizeof(*info));
*out_actual = sizeof(*info);
return ZX_OK;
}
case IOCTL_BLOCK_FVM_QUERY: {
if (max < sizeof(fvm_info_t))
return ZX_ERR_BUFFER_TOO_SMALL;
fvm_info_t* info = static_cast<fvm_info_t*>(reply);
info->slice_size = mgr_->SliceSize();
info->vslice_count = mgr_->VSliceMax();
*out_actual = sizeof(fvm_info_t);
return ZX_OK;
}
case IOCTL_BLOCK_GET_TYPE_GUID: {
char* guid = static_cast<char*>(reply);
if (max < FVM_GUID_LEN)
return ZX_ERR_BUFFER_TOO_SMALL;
fbl::AutoLock lock(&lock_);
if (IsKilledLocked())
return ZX_ERR_BAD_STATE;
memcpy(guid, mgr_->GetAllocatedVPartEntry(entry_index_)->type, FVM_GUID_LEN);
*out_actual = FVM_GUID_LEN;
return ZX_OK;
}
case IOCTL_BLOCK_GET_PARTITION_GUID: {
char* guid = static_cast<char*>(reply);
if (max < FVM_GUID_LEN)
return ZX_ERR_BUFFER_TOO_SMALL;
fbl::AutoLock lock(&lock_);
if (IsKilledLocked())
return ZX_ERR_BAD_STATE;
memcpy(guid, mgr_->GetAllocatedVPartEntry(entry_index_)->guid, FVM_GUID_LEN);
*out_actual = FVM_GUID_LEN;
return ZX_OK;
}
case IOCTL_BLOCK_GET_NAME: {
char* name = static_cast<char*>(reply);
if (max < FVM_NAME_LEN + 1)
return ZX_ERR_BUFFER_TOO_SMALL;
fbl::AutoLock lock(&lock_);
if (IsKilledLocked())
return ZX_ERR_BAD_STATE;
memcpy(name, mgr_->GetAllocatedVPartEntry(entry_index_)->name, FVM_NAME_LEN);
name[FVM_NAME_LEN] = 0;
*out_actual = strlen(name);
return ZX_OK;
}
case IOCTL_DEVICE_SYNC: {
// Propagate sync to parent device
return device_ioctl(GetParent(), IOCTL_DEVICE_SYNC, nullptr, 0, nullptr, 0, nullptr);
}
case IOCTL_BLOCK_FVM_EXTEND: {
if (cmdlen < sizeof(extend_request_t))
return ZX_ERR_BUFFER_TOO_SMALL;
const extend_request_t* request = static_cast<const extend_request_t*>(cmd);
zx_status_t status;
if ((status = RequestBoundCheck(request, mgr_->VSliceMax())) != ZX_OK) {
return status;
} else if (request->length == 0) {
return ZX_OK;
}
return mgr_->AllocateSlices(this, request->offset, request->length);
}
case IOCTL_BLOCK_FVM_SHRINK: {
if (cmdlen < sizeof(extend_request_t))
return ZX_ERR_BUFFER_TOO_SMALL;
const extend_request_t* request = static_cast<const extend_request_t*>(cmd);
zx_status_t status;
if ((status = RequestBoundCheck(request, mgr_->VSliceMax())) != ZX_OK) {
return status;
} else if (request->length == 0) {
return ZX_OK;
}
return mgr_->FreeSlices(this, request->offset, request->length);
}
case IOCTL_BLOCK_FVM_DESTROY: {
return mgr_->FreeSlices(this, 0, mgr_->VSliceMax());
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
typedef struct multi_iotxn_state {
multi_iotxn_state(size_t total, iotxn_t* txn)
: txns_completed(0), txns_total(total), status(ZX_OK), original(txn) {}
fbl::Mutex lock;
size_t txns_completed TA_GUARDED(lock);
size_t txns_total TA_GUARDED(lock);
zx_status_t status TA_GUARDED(lock);
iotxn_t* original TA_GUARDED(lock);
} multi_iotxn_state_t;
static void multi_iotxn_completion(iotxn_t* txn, void* cookie) {
multi_iotxn_state_t* state = static_cast<multi_iotxn_state_t*>(cookie);
bool last_iotxn = false;
{
fbl::AutoLock lock(&state->lock);
state->txns_completed++;
if (state->status == ZX_OK && txn->status != ZX_OK) {
state->status = txn->status;
}
if (state->txns_completed == state->txns_total) {
last_iotxn = true;
iotxn_complete(state->original, state->status, state->original->length);
}
}
if (last_iotxn) {
delete state;
}
iotxn_release(txn);
}
void VPartition::DdkIotxnQueue(iotxn_t* txn) {
if (txn->offset % BlockSize()) {
iotxn_complete(txn, ZX_ERR_INVALID_ARGS, 0);
return;
}
// transactions from read()/write() may be truncated
txn->length = ROUNDDOWN(txn->length, BlockSize());
if (txn->length == 0) {
iotxn_complete(txn, ZX_OK, 0);
return;
}
const size_t disk_size = mgr_->DiskSize();
const size_t slice_size = mgr_->SliceSize();
size_t vslice_start = txn->offset / slice_size;
size_t vslice_end = (txn->offset + txn->length - 1) / slice_size;
fbl::AutoLock lock(&lock_);
if (vslice_start == vslice_end) {
// Common case: iotxn occurs within one slice
uint32_t pslice = SliceGetLocked(vslice_start);
if (pslice == FVM_SLICE_FREE) {
iotxn_complete(txn, ZX_ERR_OUT_OF_RANGE, 0);
return;
}
txn->offset = SliceStart(disk_size, slice_size, pslice) +
(txn->offset % slice_size);
iotxn_queue(GetParent(), txn);
return;
}
// Less common case: iotxn 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++) {
if (SliceGetLocked(vslice) == FVM_SLICE_FREE) {
iotxn_complete(txn, ZX_ERR_OUT_OF_RANGE, 0);
return;
}
if (vslice != vslice_start && SliceGetLocked(vslice - 1) + 1 != SliceGetLocked(vslice)) {
contiguous = false;
}
}
// Ideal case: slices are contiguous
if (contiguous) {
uint32_t pslice = SliceGetLocked(vslice_start);
txn->offset = SliceStart(disk_size, slice_size, pslice) +
(txn->offset % slice_size);
iotxn_queue(GetParent(), txn);
return;
}
// Harder case: Noncontiguous slices
constexpr size_t kMaxSlices = 32;
iotxn_t* txns[kMaxSlices];
const size_t txn_count = vslice_end - vslice_start + 1;
if (kMaxSlices < (txn_count)) {
iotxn_complete(txn, ZX_ERR_OUT_OF_RANGE, 0);
return;
}
fbl::AllocChecker ac;
fbl::unique_ptr<multi_iotxn_state_t> state(new (&ac) multi_iotxn_state_t(txn_count, txn));
if (!ac.check()) {
iotxn_complete(txn, ZX_ERR_NO_MEMORY, 0);
return;
}
size_t length_remaining = txn->length;
for (size_t i = 0; i < txn_count; i++) {
size_t vslice = vslice_start + i;
uint32_t pslice = SliceGetLocked(vslice);
uint64_t vmo_offset;
zx_off_t length;
if (vslice == vslice_start) {
length = fbl::roundup(txn->offset + 1, slice_size) - txn->offset;
vmo_offset = 0;
} else if (vslice == vslice_end) {
length = length_remaining;
vmo_offset = txn->length - length_remaining;
} else {
length = slice_size;
vmo_offset = txns[0]->length + slice_size * (i - 1);
}
ZX_DEBUG_ASSERT(length <= slice_size);
zx_status_t status;
txns[i] = nullptr;
if ((status = iotxn_clone_partial(txn, vmo_offset, length, &txns[i])) != ZX_OK) {
while (i-- > 0) {
iotxn_release(txns[i]);
}
iotxn_complete(txn, status, 0);
return;
}
txns[i]->offset = SliceStart(disk_size, slice_size, pslice);
if (vslice == vslice_start) {
txns[i]->offset += (txn->offset % slice_size);
}
length_remaining -= txns[i]->length;
txns[i]->complete_cb = multi_iotxn_completion;
txns[i]->cookie = state.get();
}
ZX_DEBUG_ASSERT(length_remaining == 0);
for (size_t i = 0; i < txn_count; i++) {
iotxn_queue(GetParent(), txns[i]);
}
state.release();
}
zx_off_t VPartition::DdkGetSize() {
const zx_off_t size = mgr_->DiskSize() * mgr_->SliceSize();
if (size / mgr_->SliceSize() != size) {
return fbl::numeric_limits<zx_off_t>::max();
}
return size;
}
void VPartition::DdkUnbind() {
device_remove(mxdev());
}
void VPartition::DdkRelease() {
delete this;
}
// Block Protocol (VPartition)
void VPartition::BlockSetCallbacks(block_callbacks_t* cb) {
callbacks_ = cb;
}
void VPartition::BlockGetInfo(block_info_t* info) {
fbl::AutoLock lock(&lock_);
*info = info_;
}
void VPartition::BlockRead(zx_handle_t vmo, uint64_t length,
uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
Txn(IOTXN_OP_READ, vmo, length, vmo_offset, dev_offset, cookie);
}
void VPartition::BlockWrite(zx_handle_t vmo, uint64_t length, uint64_t vmo_offset,
uint64_t dev_offset, void* cookie) {
Txn(IOTXN_OP_WRITE, vmo, length, vmo_offset, dev_offset, cookie);
}
} // namespace fvm
// C-compatibility definitions
static zx_status_t fvm_load_thread(void* arg) {
return reinterpret_cast<fvm::VPartitionManager*>(arg)->Load();
}
zx_status_t fvm_bind(zx_device_t* parent) {
fbl::unique_ptr<fvm::VPartitionManager> vpm;
zx_status_t status = fvm::VPartitionManager::Create(parent, &vpm);
if (status != ZX_OK) {
return status;
}
// Read vpartition table asynchronously
status = thrd_create_with_name(&vpm->init_, fvm_load_thread, vpm.get(), "fvm-init");
if (status < 0) {
return status;
}
vpm.release();
return ZX_OK;
}