blob: 47969e405b36e346942e7a29b841688b6c44fe32 [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 <threads.h>
#include <unistd.h>
#include <ddk/protocol/block.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/array.h>
#include <fbl/limits.h>
#include <fbl/new.h>
#include <fs/mapped-vmo.h>
#include <sync/completion.h>
#include <zircon/compiler.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <zircon/thread_annotations.h>
#include <lib/zx/vmo.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,
size_t block_op_size, const block_protocol_t* bp)
: ManagerDeviceType(parent), info_(info), metadata_(nullptr), metadata_size_(0),
slice_size_(0), block_op_size_(block_op_size) {
memcpy(&bp_, bp, sizeof(*bp));
}
VPartitionManager::~VPartitionManager() = default;
zx_status_t VPartitionManager::Create(zx_device_t* dev, fbl::unique_ptr<VPartitionManager>* out) {
block_info_t block_info;
block_protocol_t bp;
size_t block_op_size = 0;
if (device_get_protocol(dev, ZX_PROTOCOL_BLOCK, &bp) != ZX_OK) {
printf("fvm: ERROR: block device '%s': does not support block protocol\n",
device_get_name(dev));
return ZX_ERR_NOT_SUPPORTED;
}
bp.ops->query(bp.ctx, &block_info, &block_op_size);
fbl::AllocChecker ac;
auto vpm = fbl::make_unique_checked<VPartitionManager>(&ac, dev, block_info,
block_op_size, &bp);
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;
}
// TODO(johngro): ask smklein why it is OK to release this managed pointer.
__UNUSED auto ptr = vp.release();
return ZX_OK;
}
struct VpmIoCookie {
fbl::atomic<size_t> num_txns;
fbl::atomic<zx_status_t> status;
completion_t signal;
};
static void IoCallback(block_op_t* op, zx_status_t status) {
VpmIoCookie* c = reinterpret_cast<VpmIoCookie*>(op->cookie);
if (status != ZX_OK) {
c->status.store(status);
}
if (c->num_txns.fetch_sub(1) - 1 == 0) {
completion_signal(&c->signal);
}
}
zx_status_t VPartitionManager::DoIoLocked(zx_handle_t vmo, size_t off,
size_t len, uint32_t command) {
size_t block_size = info_.block_size;
size_t max_transfer = info_.max_transfer_size / block_size;
size_t len_remaining = len / block_size;
size_t vmo_offset = 0;
size_t dev_offset = off / block_size;
size_t num_txns = fbl::round_up(len_remaining, max_transfer) / max_transfer;
fbl::AllocChecker ac;
fbl::Array<uint8_t> buffer(new (&ac) uint8_t[block_op_size_ * num_txns],
block_op_size_ * num_txns);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
VpmIoCookie cookie;
cookie.num_txns.store(num_txns);
cookie.status.store(ZX_OK);
completion_reset(&cookie.signal);
for (size_t i = 0; i < num_txns; i++) {
size_t length = fbl::min(len_remaining, max_transfer);
len_remaining -= length;
block_op_t* bop = reinterpret_cast<block_op_t*>(buffer.get() + (block_op_size_ * i));
bop->command = command;
bop->rw.vmo = vmo;
bop->rw.length = static_cast<uint32_t>(length);
bop->rw.offset_dev = dev_offset;
bop->rw.offset_vmo = vmo_offset;
bop->rw.pages = NULL;
bop->completion_cb = IoCallback;
bop->cookie = &cookie;
memset(buffer.get() + (block_op_size_ * i) + sizeof(block_op_t), 0,
block_op_size_ - sizeof(block_op_t));
bp_.ops->queue(bp_.ctx, bop);
vmo_offset += length;
dev_offset += length;
}
ZX_DEBUG_ASSERT(len_remaining == 0);
completion_wait(&cookie.signal, ZX_TIME_INFINITE);
return static_cast<zx_status_t>(cookie.status.load());
}
zx_status_t VPartitionManager::Load() {
fbl::AutoLock lock(&lock_);
auto auto_detach = fbl::MakeAutoCall([&]() TA_NO_THREAD_SAFETY_ANALYSIS {
fprintf(stderr, "fvm: Aborting Driver Load\n");
DdkRemove();
// "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;
});
zx::vmo vmo;
if (zx::vmo::create(FVM_BLOCK_SIZE, 0, &vmo) != ZX_OK) {
return ZX_ERR_INTERNAL;
}
// Read the superblock first, to determine the slice sice
if (DoIoLocked(vmo.get(), 0, FVM_BLOCK_SIZE, BLOCK_OP_READ)) {
fprintf(stderr, "fvm: Failed to read first block from underlying device\n");
return ZX_ERR_INTERNAL;
}
fvm_t sb;
zx_status_t status = vmo.read(&sb, 0, sizeof(sb));
if (status != ZX_OK) {
return ZX_ERR_INTERNAL;
}
// Validate the superblock, confirm the slice size
slice_size_ = sb.slice_size;
if ((slice_size_ * VSliceMax()) / VSliceMax() != slice_size_) {
fprintf(stderr, "fvm: Slice Size, VSliceMax overflow block address space\n");
return ZX_ERR_BAD_STATE;
} else if (info_.block_size == 0 || SliceSize() % info_.block_size) {
fprintf(stderr, "fvm: Bad block (%u) or slice size (%zu)\n",
info_.block_size, SliceSize());
return ZX_ERR_BAD_STATE;
} else if (sb.vpartition_table_size != kVPartTableLength) {
fprintf(stderr, "fvm: Bad vpartition table size %zu (expected %zu)\n",
sb.vpartition_table_size, kVPartTableLength);
return ZX_ERR_BAD_STATE;
} else if (sb.allocation_table_size != AllocTableLength(DiskSize(), SliceSize())) {
fprintf(stderr, "fvm: Bad allocation table size %zu (expected %zu)\n",
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
auto make_metadata_vmo = [&](size_t offset, fbl::unique_ptr<fs::MappedVmo>* out) {
fbl::unique_ptr<fs::MappedVmo> mvmo;
zx_status_t status = fs::MappedVmo::Create(MetadataSize(), "fvm-meta", &mvmo);
if (status != ZX_OK) {
return status;
}
// Read both copies of metadata, ensure at least one is valid
if ((status = DoIoLocked(mvmo->GetVmo(), offset,
MetadataSize(), BLOCK_OP_READ)) != ZX_OK) {
return status;
}
*out = fbl::move(mvmo);
return ZX_OK;
};
fbl::unique_ptr<fs::MappedVmo> mvmo;
if ((status = make_metadata_vmo(0, &mvmo)) != ZX_OK) {
fprintf(stderr, "fvm: Failed to load metadata vmo: %d\n", status);
return status;
}
fbl::unique_ptr<fs::MappedVmo> mvmo_backup;
if ((status = make_metadata_vmo(MetadataSize(), &mvmo_backup)) != ZX_OK) {
fprintf(stderr, "fvm: Failed to load backup metadata vmo: %d\n", status);
return status;
}
const void* metadata;
if ((status = fvm_validate_header(mvmo->GetData(), mvmo_backup->GetData(),
MetadataSize(), &metadata)) != ZX_OK) {
fprintf(stderr, "fvm: Header validation failure: %d\n", status);
return status;
}
if (metadata == mvmo->GetData()) {
first_metadata_is_primary_ = true;
metadata_ = fbl::move(mvmo);
} else {
first_metadata_is_primary_ = false;
metadata_ = fbl::move(mvmo_backup);
}
// Begin initializing the underlying partitions
DdkMakeVisible();
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) {
fprintf(stderr, "FVM: Failed to Create vpartition %zu\n", i);
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_ENTRY_FREE) {
continue;
}
if (vpartitions[entry->Vpart()] == nullptr) {
continue;
}
// 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;
} else if (GetAllocatedVPartEntry(i)->flags & kVPartFlagInactive) {
fprintf(stderr, "FVM: Freeing inactive partition\n");
FreeSlices(vpartitions[i].get(), 0, VSliceMax());
continue;
} else if (AddPartition(fbl::move(vpartitions[i]))) {
continue;
}
device_count++;
}
return ZX_OK;
}
zx_status_t VPartitionManager::WriteFvmLocked() {
zx_status_t status;
GetFvmLocked()->generation++;
fvm_update_hash(GetFvmLocked(), MetadataSize());
// If we were reading from the primary, write to the backup.
status = DoIoLocked(metadata_->GetVmo(), BackupOffsetLocked(),
MetadataSize(), BLOCK_OP_WRITE);
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))->SetVpart(FVM_SLICE_ENTRY_FREE);
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->SetVpart(vpart);
alloc_entry->SetVslice(vslice);
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))->SetVpart(FVM_SLICE_ENTRY_FREE);
vp->SliceFreeLocked(vslice);
}
}
return status;
}
zx_status_t VPartitionManager::Upgrade(const uint8_t* old_guid, const uint8_t* new_guid) {
fbl::AutoLock lock(&lock_);
size_t old_index = 0;
size_t new_index = 0;
if (!memcmp(old_guid, new_guid, GUID_LEN)) {
old_guid = nullptr;
}
for (size_t i = 1; i < FVM_MAX_ENTRIES; i++) {
auto entry = GetVPartEntryLocked(i);
if (entry->slices != 0) {
if (old_guid && !(entry->flags & kVPartFlagInactive) &&
!memcmp(entry->guid, old_guid, GUID_LEN)) {
old_index = i;
} else if ((entry->flags & kVPartFlagInactive) &&
!memcmp(entry->guid, new_guid, GUID_LEN)) {
new_index = i;
}
}
}
if (!new_index) {
return ZX_ERR_NOT_FOUND;
}
if (old_index) {
GetVPartEntryLocked(old_index)->flags |= kVPartFlagInactive;
}
GetVPartEntryLocked(new_index)->flags &= ~kVPartFlagInactive;
return WriteFvmLocked();
}
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;
//TODO: use block protocol
// 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()) {
for (size_t i = extent->start(); i < extent->end(); i++) {
GetSliceEntryLocked(vp->SliceGetLocked(i))->SetVpart(FVM_SLICE_ENTRY_FREE);
}
vp->ExtentDestroyLocked(extent->start());
}
// Remove device, VPartition if this was a request to free all slices.
vp->DdkRemove();
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)->SetVpart(FVM_SLICE_ENTRY_FREE);
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_PARTITION: {
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, request->flags & kVPartAllocateMask);
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;
}
case IOCTL_BLOCK_FVM_UPGRADE: {
if (cmdlen < sizeof(upgrade_req_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
const upgrade_req_t* req = static_cast<const upgrade_req_t*>(cmd);
return Upgrade(req->old_guid, req->new_guid);
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_ERR_NOT_SUPPORTED;
}
void VPartitionManager::DdkUnbind() {
DdkRemove();
}
void VPartitionManager::DdkRelease() {
thrd_join(init_, nullptr);
delete this;
}
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);
fbl::AllocChecker ac;
auto vp = fbl::make_unique_checked<VPartition>(&ac, vpm, entry_index, vpm->BlockOpSize());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*out = fbl::move(vp);
return ZX_OK;
}
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::CheckSlices(size_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;
}
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;
}
} else {
// 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));
extent = --slice_map_.upper_bound(vslice);
}
ZX_DEBUG_ASSERT(SliceGetLocked(vslice) == pslice);
AddBlocksLocked((mgr_->SliceSize() / info_.block_size));
// Merge with the next contiguous extent (if any)
auto nextExtent = slice_map_.upper_bound(vslice);
if (nextExtent.IsValid() && (vslice + 1 == nextExtent->start())) {
if (extent->Merge(*nextExtent)) {
slice_map_.erase(*nextExtent);
}
}
return ZX_OK;
}
bool VPartition::SliceFreeLocked(size_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);
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::ExtentDestroyLocked(size_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));
}
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_VSLICE_QUERY: {
if (cmdlen < sizeof(query_request_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
if (max < sizeof(query_response_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
const query_request_t* request = static_cast<const query_request_t*>(cmd);
if (request->count > MAX_FVM_VSLICE_REQUESTS) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
query_response_t* response = static_cast<query_response_t*>(reply);
response->count = 0;
for (size_t i = 0; i < request->count; i++) {
zx_status_t status;
if ((status = CheckSlices(request->vslice_start[i], &response->vslice_range[i].count,
&response->vslice_range[i].allocated)) != ZX_OK) {
return status;
}
response->count++;
}
*out_actual = sizeof(query_response_t);
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_PARTITION: {
return mgr_->FreeSlices(this, 0, mgr_->VSliceMax());
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
typedef struct multi_txn_state {
multi_txn_state(size_t total, block_op_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);
block_op_t* original TA_GUARDED(lock);
} multi_txn_state_t;
static void multi_txn_completion(block_op_t* txn, zx_status_t status) {
multi_txn_state_t* state = static_cast<multi_txn_state_t*>(txn->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->original->completion_cb(state->original, state->status);
}
}
if (last_txn) {
delete state;
}
free(txn);
}
void VPartition::BlockQueue(block_op_t* txn) {
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);
return;
default:
fprintf(stderr, "[FVM BlockQueue] Unsupported Command: %x\n", txn->command);
txn->completion_cb(txn, ZX_ERR_NOT_SUPPORTED);
return;
}
const uint64_t device_capacity = DdkGetSize() / BlockSize();
if (txn->rw.length == 0) {
txn->completion_cb(txn, ZX_ERR_INVALID_ARGS);
return;
} else if ((txn->rw.offset_dev >= device_capacity) ||
(device_capacity - txn->rw.offset_dev < txn->rw.length)) {
txn->completion_cb(txn, ZX_ERR_OUT_OF_RANGE);
return;
}
const size_t disk_size = mgr_->DiskSize();
const size_t slice_size = mgr_->SliceSize();
const uint64_t blocks_per_slice = slice_size / BlockSize();
// Start, end both inclusive
size_t vslice_start = txn->rw.offset_dev / blocks_per_slice;
size_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
uint32_t pslice = SliceGetLocked(vslice_start);
if (pslice == PSLICE_UNALLOCATED) {
txn->completion_cb(txn, ZX_ERR_OUT_OF_RANGE);
return;
}
txn->rw.offset_dev = SliceStart(disk_size, slice_size, pslice) /
BlockSize() + (txn->rw.offset_dev % blocks_per_slice);
mgr_->Queue(txn);
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++) {
if (SliceGetLocked(vslice) == PSLICE_UNALLOCATED) {
txn->completion_cb(txn, ZX_ERR_OUT_OF_RANGE);
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->rw.offset_dev = SliceStart(disk_size, slice_size, pslice) /
BlockSize() + (txn->rw.offset_dev % blocks_per_slice);
mgr_->Queue(txn);
return;
}
// Harder case: Noncontiguous slices
constexpr size_t kMaxSlices = 32;
block_op_t* txns[kMaxSlices];
const size_t txn_count = vslice_end - vslice_start + 1;
if (kMaxSlices < txn_count) {
txn->completion_cb(txn, ZX_ERR_OUT_OF_RANGE);
return;
}
fbl::AllocChecker ac;
fbl::unique_ptr<multi_txn_state_t> state(new (&ac) multi_txn_state_t(txn_count, txn));
if (!ac.check()) {
txn->completion_cb(txn, ZX_ERR_NO_MEMORY);
return;
}
uint32_t length_remaining = txn->rw.length;
for (size_t i = 0; i < txn_count; i++) {
size_t vslice = vslice_start + i;
uint32_t pslice = SliceGetLocked(vslice);
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[i] = static_cast<block_op_t*>(calloc(1, mgr_->BlockOpSize()));
if (txns[i] == nullptr) {
while (i-- > 0) {
free(txns[i]);
}
txn->completion_cb(txn, ZX_ERR_NO_MEMORY);
return;
}
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 = SliceStart(disk_size, slice_size, pslice) / BlockSize();
if (vslice == vslice_start) {
txns[i]->rw.offset_dev += (txn->rw.offset_dev % blocks_per_slice);
}
length_remaining -= txns[i]->rw.length;
txns[i]->completion_cb = multi_txn_completion;
txns[i]->cookie = state.get();
}
ZX_DEBUG_ASSERT(length_remaining == 0);
for (size_t i = 0; i < txn_count; i++) {
mgr_->Queue(txns[i]);
}
// TODO(johngro): ask smklein why it is OK to release this managed pointer.
__UNUSED auto ptr = state.release();
}
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;
}
void VPartition::BlockQuery(block_info_t* info_out, size_t* block_op_size_out) {
static_assert(fbl::is_same<decltype(info_out), decltype(&info_)>::value, "Info type mismatch");
memcpy(info_out, &info_, sizeof(info_));
*block_op_size_out = mgr_->BlockOpSize();
}
} // 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;
}
if ((status = vpm->DdkAdd("fvm", DEVICE_ADD_INVISIBLE)) != 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) {
vpm->DdkRemove();
return status;
}
// TODO(johngro): ask smklein why it is OK to release this managed pointer.
__UNUSED auto ptr = vpm.release();
return ZX_OK;
}