blob: e470c05e1127533c169524d41ff2047ef72b7bb3 [file] [log] [blame]
// Copyright 2021 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 "src/storage/f2fs/bcache.h"
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <fbl/alloc_checker.h>
#include <fbl/ref_ptr.h>
#include <storage/buffer/block_buffer.h>
#include <storage/buffer/vmo_buffer.h>
#include <storage/operation/operation.h>
#include "src/storage/f2fs/f2fs_layout.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
namespace f2fs {
zx::result<std::unique_ptr<BcacheMapper>> CreateBcacheMapper(
std::vector<std::unique_ptr<block_client::BlockDevice>> devices, bool* out_readonly) {
uint64_t total_block_count = 0;
bool readonly = false;
std::vector<std::unique_ptr<Bcache>> bcaches;
for (auto& device : devices) {
fuchsia_hardware_block::wire::BlockInfo info;
if (zx_status_t status = device->BlockGetInfo(&info); status != ZX_OK) {
FX_LOGS(ERROR) << "Could not access device info: " << status;
return zx::error(status);
}
if (info.block_size == 0 || kBlockSize % info.block_size != 0) {
FX_LOGS(WARNING) << "f2fs block size must be multiple of underlying block size: "
<< info.block_size;
return zx::error(ZX_ERR_BAD_STATE);
}
if (info.flags & fuchsia_hardware_block::wire::Flag::kReadonly) {
readonly = true;
}
uint64_t block_count = info.block_size * info.block_count / kBlockSize;
auto bcache_or = Bcache::Create(std::move(device), block_count, kBlockSize);
if (bcache_or.is_error()) {
return bcache_or.take_error();
}
bcaches.push_back(std::move(bcache_or.value()));
total_block_count += block_count;
}
// F2fs requires at least 8 segments (sb + (ckpt + sit + nat) * 2 + ssr) for its metadata, so the
// total_block_count must be greater than 8 segments to store data.
if (total_block_count <= kDefaultBlocksPerSegment * 8) {
FX_LOGS(ERROR) << "block device is too small";
return zx::error(ZX_ERR_NO_SPACE);
}
// The maximum volume size of f2fs is 16TiB
if (total_block_count >= std::numeric_limits<uint32_t>::max()) {
FX_LOGS(ERROR) << "block device is too large (> 16TiB)";
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
if (out_readonly) {
*out_readonly = readonly;
}
return BcacheMapper::Create(std::move(bcaches));
}
zx::result<std::unique_ptr<BcacheMapper>> CreateBcacheMapper(
std::unique_ptr<block_client::BlockDevice> device, bool* out_readonly) {
std::vector<std::unique_ptr<block_client::BlockDevice>> devices;
devices.push_back(std::move(device));
return CreateBcacheMapper(std::move(devices), out_readonly);
}
zx::result<std::unique_ptr<BcacheMapper>> CreateBcacheMapper(
fidl::ClientEnd<fuchsia_hardware_block::Block> device_channel, bool* out_readonly) {
auto device_or = block_client::RemoteBlockDevice::Create(
fidl::ClientEnd<fuchsia_hardware_block_volume::Volume>{device_channel.TakeChannel()});
if (device_or.is_error()) {
FX_LOGS(ERROR) << "could not initialize block device";
return device_or.take_error();
}
bool readonly_device = false;
auto bc_or = CreateBcacheMapper(std::move(*device_or), &readonly_device);
if (bc_or.is_error()) {
FX_LOGS(ERROR) << "could not create block cache";
return bc_or.take_error();
}
return bc_or.take_value();
}
Bcache::Bcache(std::unique_ptr<block_client::BlockDevice> device, uint64_t max_blocks,
block_t block_size)
: max_blocks_(max_blocks), block_size_(block_size), device_(std::move(device)) {}
zx_status_t Bcache::BlockAttachVmo(const zx::vmo& vmo, storage::Vmoid* out) {
return GetDevice()->BlockAttachVmo(vmo, out);
}
zx_status_t Bcache::BlockDetachVmo(storage::Vmoid vmoid) {
return GetDevice()->BlockDetachVmo(std::move(vmoid));
}
zx::result<std::unique_ptr<Bcache>> Bcache::Create(
std::unique_ptr<block_client::BlockDevice> device, uint64_t max_blocks, block_t block_size) {
std::unique_ptr<Bcache> bcache(new Bcache(std::move(device), max_blocks, block_size));
zx_status_t status = bcache->VerifyDeviceInfo();
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(bcache));
}
zx_status_t Bcache::VerifyDeviceInfo() {
zx_status_t status = device_->BlockGetInfo(&info_);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "cannot get block device information: " << status;
return status;
}
return ZX_OK;
}
zx_status_t BcacheMapper::Readblk(block_t bno, void* data) {
if (bno >= Maxblk()) {
return ZX_ERR_OUT_OF_RANGE;
}
std::lock_guard lock(buffer_mutex_);
zx_status_t status =
RunRequests({storage::BufferedOperation{.vmoid = buffer_.vmoid(),
.op = {.type = storage::OperationType::kRead,
.vmo_offset = 0,
.dev_offset = bno,
.length = 1}}});
if (status != ZX_OK) {
return status;
}
std::memcpy(data, buffer_.Data(0), BlockSize());
return ZX_OK;
}
zx_status_t BcacheMapper::Writeblk(block_t bno, const void* data) {
if (bno >= Maxblk()) {
return ZX_ERR_OUT_OF_RANGE;
}
std::lock_guard lock(buffer_mutex_);
std::memcpy(buffer_.Data(0), data, BlockSize());
return RunRequests({storage::BufferedOperation{.vmoid = buffer_.vmoid(),
.op = {.type = storage::OperationType::kWrite,
.vmo_offset = 0,
.dev_offset = bno,
.length = 1}}});
}
zx_status_t BcacheMapper::Trim(block_t start, block_t num) {
if (!(info_.flags & fuchsia_hardware_block::wire::Flag::kTrimSupport)) {
return ZX_ERR_NOT_SUPPORTED;
}
return RunRequests({storage::BufferedOperation{.vmoid = BLOCK_VMOID_INVALID,
.op = {
.type = storage::OperationType::kTrim,
.vmo_offset = 0,
.dev_offset = start,
.length = num,
}}});
}
zx_status_t BcacheMapper::Flush() {
for (auto& bcache : bcaches_) {
if (auto err = bcache->Flush(); err != ZX_OK) {
return err;
}
}
return ZX_OK;
}
zx::result<std::unique_ptr<BcacheMapper>> BcacheMapper::Create(
std::vector<std::unique_ptr<Bcache>> bcaches) {
uint64_t total_block_count = 0;
for (auto& bcache : bcaches) {
ZX_ASSERT(bcache->BlockSize() == kBlockSize);
total_block_count += bcache->Maxblk();
}
auto bcache = std::unique_ptr<BcacheMapper>(
new BcacheMapper(std::move(bcaches), total_block_count, kBlockSize));
zx_status_t status = bcache->CreateVmoBuffer();
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(bcache));
}
zx_status_t BcacheMapper::RunRequests(const std::vector<storage::BufferedOperation>& operations) {
std::vector<std::vector<storage::BufferedOperation>> new_operations(bcaches_.size());
for (const auto& operation : operations) {
if (operation.op.dev_offset + operation.op.length > Maxblk()) {
return ZX_ERR_OUT_OF_RANGE;
}
uint64_t residual = operation.op.length;
uint64_t offset = operation.op.dev_offset;
for (size_t i = 0; i < bcaches_.size(); ++i) {
if (offset >= bcaches_[i]->Maxblk()) {
offset -= bcaches_[i]->Maxblk();
continue;
}
uint64_t length;
if (residual <= bcaches_[i]->Maxblk() - offset) {
length = residual;
} else {
length = bcaches_[i]->Maxblk() - offset;
}
vmoid_t vmoid;
if (operation.vmoid == BLOCK_VMOID_INVALID) {
if (operation.op.type != storage::OperationType::kTrim) {
return ZX_ERR_INVALID_ARGS;
}
vmoid = BLOCK_VMOID_INVALID;
} else {
vmoid = vmoid_tree_[operation.vmoid][i].get();
}
new_operations[i].push_back(storage::BufferedOperation{
.vmoid = vmoid,
.op = storage::Operation{
.type = operation.op.type,
.vmo_offset = operation.op.vmo_offset + operation.op.length - residual,
.dev_offset = offset,
.length = length,
.trace_flow_id = operation.op.trace_flow_id,
}});
residual -= length;
offset = 0;
if (residual == 0) {
break;
}
}
}
for (size_t i = 0; i < bcaches_.size(); ++i) {
if (new_operations[i].empty()) {
continue;
}
if (auto err = bcaches_[i]->RunRequests(new_operations[i]); err != ZX_OK) {
return err;
}
}
return ZX_OK;
}
zx::result<vmoid_t> BcacheMapper::FindFreeVmoId() {
for (vmoid_t i = last_id_; i < std::numeric_limits<vmoid_t>::max(); ++i) {
if (vmoid_tree_.find(i) == vmoid_tree_.end()) {
last_id_ = static_cast<vmoid_t>(i + 1);
return zx::ok(i);
}
}
for (vmoid_t i = BLOCK_VMOID_INVALID + 1; i < last_id_; ++i) {
if (vmoid_tree_.find(i) == vmoid_tree_.end()) {
last_id_ = static_cast<vmoid_t>(i + 1);
return zx::ok(i);
}
}
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx_status_t BcacheMapper::BlockAttachVmo(const zx::vmo& vmo, storage::Vmoid* out) {
zx::result<vmoid_t> vmoid_or = FindFreeVmoId();
if (vmoid_or.is_error()) {
return vmoid_or.error_value();
}
std::vector<storage::Vmoid> new_vmoids;
auto cleanup = fit::defer([&] {
for (size_t i = 0; i < new_vmoids.size(); ++i) {
bcaches_[i]->BlockDetachVmo(std::move(new_vmoids[i]));
}
});
for (auto& bcache : bcaches_) {
storage::Vmoid vmoid;
if (auto ret = bcache->BlockAttachVmo(vmo, &vmoid); ret != ZX_OK) {
return ret;
}
new_vmoids.push_back(std::move(vmoid));
}
vmoid_tree_.insert(std::make_pair(vmoid_or.value(), std::move(new_vmoids)));
*out = storage::Vmoid(vmoid_or.value());
cleanup.cancel();
return ZX_OK;
}
zx_status_t BcacheMapper::BlockDetachVmo(storage::Vmoid vmoid) {
auto it = vmoid_tree_.find(vmoid.get());
if (it == vmoid_tree_.end()) {
return ZX_ERR_NOT_FOUND;
}
ZX_DEBUG_ASSERT(it->second.size() == bcaches_.size());
auto& vmoids = it->second;
for (size_t i = 0; i < bcaches_.size(); ++i) {
if (auto ret = bcaches_[i]->BlockDetachVmo(std::move(vmoids[i])); ret != ZX_OK) {
return ret;
}
}
vmoid_tree_.erase(it);
[[maybe_unused]] auto leak = vmoid.TakeId();
return ZX_OK;
}
BcacheMapper::BcacheMapper(std::vector<std::unique_ptr<Bcache>> bcaches, uint64_t max_blocks,
block_t block_size)
: bcaches_(std::move(bcaches)), block_size_(block_size), max_blocks_(max_blocks) {
uint32_t transfer_size = MAX_TRANSFER_UNBOUNDED;
uint32_t max_block_size = 0;
fuchsia_hardware_block::wire::Flag flag = fuchsia_hardware_block::wire::Flag::kRemovable |
fuchsia_hardware_block::wire::Flag::kTrimSupport |
fuchsia_hardware_block::wire::Flag::kFuaSupport;
for (auto& bcache : bcaches_) {
fuchsia_hardware_block::wire::BlockInfo info;
bcache->GetDevice()->BlockGetInfo(&info);
if (max_block_size < info.block_size) {
max_block_size = info.block_size;
}
if (transfer_size > info.max_transfer_size) {
transfer_size = info.max_transfer_size;
}
if (info.flags & fuchsia_hardware_block::wire::Flag::kReadonly) {
flag |= fuchsia_hardware_block::wire::Flag::kReadonly;
}
if (!(info.flags & fuchsia_hardware_block::wire::Flag::kRemovable)) {
flag &= (~fuchsia_hardware_block::wire::Flag::kRemovable);
}
if (!(info.flags & fuchsia_hardware_block::wire::Flag::kTrimSupport)) {
flag &= (~fuchsia_hardware_block::wire::Flag::kTrimSupport);
}
if (!(info.flags & fuchsia_hardware_block::wire::Flag::kFuaSupport)) {
flag &= (~fuchsia_hardware_block::wire::Flag::kFuaSupport);
}
}
uint64_t device_block_count = max_blocks * block_size / max_block_size;
info_ = {
.block_count = device_block_count,
.block_size = max_block_size,
.max_transfer_size = transfer_size,
.flags = flag,
};
}
zx_status_t BcacheMapper::BlockGetInfo(fuchsia_hardware_block::wire::BlockInfo* out_info) const {
*out_info = info_;
return ZX_OK;
}
} // namespace f2fs