blob: 953cfe13ceca17e8477867aa87a6c398089771c6 [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 "src/devices/block/drivers/core/block-device.h"
#include <fuchsia/hardware/block/partition/cpp/banjo.h>
#include <lib/operation/block.h>
#include <lib/zbi-format/partition.h>
#include <lib/zx/profile.h>
#include <lib/zx/thread.h>
#include <threads.h>
#include <zircon/threads.h>
#include "src/devices/block/drivers/core/server.h"
#include "src/storage/lib/storage-metrics/block-metrics.h"
zx_status_t BlockDevice::DdkGetProtocol(uint32_t proto_id, void* out_protocol) {
switch (proto_id) {
case ZX_PROTOCOL_BLOCK: {
self_protocol_.GetProto(static_cast<block_protocol_t*>(out_protocol));
return ZX_OK;
}
case ZX_PROTOCOL_BLOCK_PARTITION: {
if (!parent_partition_protocol_.is_valid()) {
return ZX_ERR_NOT_SUPPORTED;
}
parent_partition_protocol_.GetProto(static_cast<block_partition_protocol_t*>(out_protocol));
return ZX_OK;
}
case ZX_PROTOCOL_BLOCK_VOLUME: {
if (!parent_volume_protocol_.is_valid()) {
return ZX_ERR_NOT_SUPPORTED;
}
parent_volume_protocol_.GetProto(static_cast<block_volume_protocol_t*>(out_protocol));
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
void BlockDevice::UpdateStats(bool success, zx::ticks start_tick, block_op_t* op) {
uint64_t bytes_transfered = op->rw.length * info_.block_size;
std::lock_guard<std::mutex> lock(stat_lock_);
stats_.UpdateStats(success, start_tick, op->command.opcode, bytes_transfered);
}
// Define the maximum I/O possible for the midlayer; this is arbitrarily
// set to the size of RIO's max payload.
//
// If a smaller value of "max_transfer_size" is defined, that will
// be used instead.
constexpr uint32_t kMaxMidlayerIO = 8192;
zx_status_t BlockDevice::DoIo(zx::vmo& vmo, size_t buf_len, zx_off_t off, zx_off_t vmo_off,
bool write) {
std::lock_guard<std::mutex> lock(io_lock_);
const size_t block_size = info_.block_size;
const size_t max_xfer = std::min(info_.max_transfer_size, kMaxMidlayerIO);
if (buf_len == 0) {
return ZX_OK;
}
if ((buf_len % block_size) || (off % block_size)) {
return ZX_ERR_INVALID_ARGS;
}
uint64_t sub_txn_offset = 0;
while (sub_txn_offset < buf_len) {
size_t sub_txn_length = std::min(buf_len - sub_txn_offset, max_xfer);
block_op_t* op = reinterpret_cast<block_op_t*>(io_op_.get());
const uint8_t opcode = write ? BLOCK_OPCODE_WRITE : BLOCK_OPCODE_READ;
op->command = {.opcode = opcode, .flags = 0};
ZX_DEBUG_ASSERT(sub_txn_length / block_size < std::numeric_limits<uint32_t>::max());
op->rw.length = static_cast<uint32_t>(sub_txn_length / block_size);
op->rw.vmo = vmo.get();
op->rw.offset_dev = (off + sub_txn_offset) / block_size;
op->rw.offset_vmo = (vmo_off + sub_txn_offset) / block_size;
sync_completion_reset(&io_signal_);
auto completion_cb = [](void* cookie, zx_status_t status, block_op_t* op) {
BlockDevice* bdev = reinterpret_cast<BlockDevice*>(cookie);
bdev->io_status_ = status;
sync_completion_signal(&bdev->io_signal_);
};
BlockQueue(op, completion_cb, this);
sync_completion_wait(&io_signal_, ZX_TIME_INFINITE);
if (io_status_ != ZX_OK) {
return io_status_;
}
sub_txn_offset += sub_txn_length;
}
return io_status_;
}
void BlockDevice::DdkRelease() { delete this; }
void BlockDevice::BlockQuery(block_info_t* block_info, size_t* op_size) {
// It is important that all devices sitting on top of the volume protocol avoid
// caching a copy of block info for query. The "block_count" field is dynamic,
// and may change during the lifetime of the volume.
size_t parent_op_size;
parent_protocol_.Query(block_info, &parent_op_size);
// Safety check that parent op size doesn't change dynamically.
ZX_DEBUG_ASSERT(parent_op_size == parent_op_size_);
*op_size = OpSize();
}
void BlockDevice::UpdateStatsAndCallCompletion(void* cookie, zx_status_t status, block_op_t* op) {
BlockDevice* block_device = static_cast<BlockDevice*>(cookie);
block::BorrowedOperation<StatsCookie> txn(op, block_device->parent_op_size_);
StatsCookie* stats_cookie = txn.private_storage();
block_device->UpdateStats(status == ZX_OK, stats_cookie->start_tick, op);
txn.Complete(status);
}
void BlockDevice::BlockQueue(block_op_t* op, block_impl_queue_callback completion_cb,
void* cookie) {
zx::ticks start_tick = zx::ticks::now();
if (completion_status_stats_) {
block::BorrowedOperation<StatsCookie> txn(op, completion_cb, cookie, parent_op_size_);
StatsCookie* stats_cookie = txn.private_storage();
stats_cookie->start_tick = start_tick;
parent_protocol_.Queue(txn.take(), UpdateStatsAndCallCompletion, this);
} else {
// Since we don't know the return status, we assume all commands succeeded.
UpdateStats(true, start_tick, op);
parent_protocol_.Queue(op, completion_cb, cookie);
}
}
void BlockDevice::GetInfo(GetInfoCompleter::Sync& completer) {
fuchsia_hardware_block::wire::BlockInfo info;
static_assert(sizeof(info) == sizeof(block_info_t));
size_t block_op_size;
parent_protocol_.Query(reinterpret_cast<block_info_t*>(&info), &block_op_size);
// Set or clear fuchsia.hardware_block/Flag.BOOTPART appropriately.
if (has_bootpart_) {
info.flags |= fuchsia_hardware_block::wire::Flag::kBootpart;
} else {
info.flags -= fuchsia_hardware_block::wire::Flag::kBootpart;
}
completer.ReplySuccess(info);
}
void BlockDevice::GetStats(GetStatsRequestView request, GetStatsCompleter::Sync& completer) {
std::lock_guard<std::mutex> lock(stat_lock_);
if (!enable_stats_) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
fuchsia_hardware_block::wire::BlockStats stats;
stats_.CopyToFidl(&stats);
if (request->clear) {
stats_.Reset();
}
completer.ReplySuccess(stats);
}
void BlockDevice::OpenSession(OpenSessionRequestView request,
OpenSessionCompleter::Sync& completer) {
zx::result server = Server::Create(&self_protocol_);
if (server.is_error()) {
request->session.Close(server.error_value());
return;
}
// TODO(https://fxbug.dev/42067206): Avoid running a thread per session; make `Server` async
// instead.
thrd_t thread;
if (thrd_create_with_name(
&thread,
+[](void* arg) {
[[maybe_unused]] zx_status_t status = reinterpret_cast<Server*>(arg)->Serve();
return 0;
},
server.value().get(), "block_server") != thrd_success) {
request->session.Close(ZX_ERR_NO_MEMORY);
return;
}
// Set a scheduling role for the block_server thread.
// This is required in order to service the blobfs-pager-thread, which is on a deadline profile.
// This will no longer be needed once we have the ability to propagate deadlines. Until then, we
// need to set deadline profiles for all threads that the blobfs-pager-thread interacts with in
// order to service page requests.
//
// Also note that this will apply to block_server threads spawned to service each block client
// (in the typical case, we have two - blobfs and minfs). The capacity of 1ms is chosen so as to
// accommodate most cases without throttling the thread. The desired capacity was 50us, but some
// tests that use a large ramdisk require a larger capacity. In the average case though on a real
// device, the block_server thread runs for less than 50us. 1ms provides us with a generous
// leeway, without hurting performance in the typical case - a thread is not penalized for not
// using its full capacity.
{
const char* role_name = "fuchsia.devices.block.drivers.core.block-server";
if (zx_status_t status = device_set_profile_by_role(zxdev(), thrd_get_zx_handle(thread),
role_name, strlen(role_name));
status != ZX_OK) {
zxlogf(WARNING, "block: Failed to apply role to block server: %s",
zx_status_get_string(status));
}
}
fidl::BindServer(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(request->session),
std::move(server.value()),
[thread](Server* server, fidl::UnbindInfo, fidl::ServerEnd<fuchsia_hardware_block::Session>) {
server->Close();
thrd_join(thread, nullptr);
});
}
void BlockDevice::ReadBlocks(ReadBlocksRequestView request, ReadBlocksCompleter::Sync& completer) {
if (zx_status_t status =
DoIo(request->vmo, request->length, request->dev_offset, request->vmo_offset, false);
status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
void BlockDevice::WriteBlocks(WriteBlocksRequestView request,
WriteBlocksCompleter::Sync& completer) {
if (zx_status_t status =
DoIo(request->vmo, request->length, request->dev_offset, request->vmo_offset, true);
status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
void BlockDevice::GetTypeGuid(GetTypeGuidCompleter::Sync& completer) {
if (!parent_partition_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
return;
}
fuchsia_hardware_block_partition::wire::Guid guid;
static_assert(sizeof(guid.value) == sizeof(guid_t));
guid_t* guid_ptr = reinterpret_cast<guid_t*>(guid.value.data());
zx_status_t status = parent_partition_protocol_.GetGuid(GUIDTYPE_TYPE, guid_ptr);
completer.Reply(status, fidl::ObjectView<decltype(guid)>::FromExternal(&guid));
}
void BlockDevice::GetInstanceGuid(GetInstanceGuidCompleter::Sync& completer) {
if (!parent_partition_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
return;
}
fuchsia_hardware_block_partition::wire::Guid guid;
static_assert(sizeof(guid.value) == sizeof(guid_t));
guid_t* guid_ptr = reinterpret_cast<guid_t*>(guid.value.data());
zx_status_t status = parent_partition_protocol_.GetGuid(GUIDTYPE_INSTANCE, guid_ptr);
completer.Reply(status, fidl::ObjectView<decltype(guid)>::FromExternal(&guid));
}
void BlockDevice::GetName(GetNameCompleter::Sync& completer) {
if (!parent_partition_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
return;
}
char name[fuchsia_hardware_block_partition::wire::kNameLength];
zx_status_t status = parent_partition_protocol_.GetName(name, sizeof(name));
completer.Reply(status,
status == ZX_OK ? fidl::StringView::FromExternal(name) : fidl::StringView{});
}
void BlockDevice::QuerySlices(QuerySlicesRequestView request,
QuerySlicesCompleter::Sync& completer) {
if (!parent_volume_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {}, {});
return;
}
fidl::Array<fuchsia_hardware_block_volume::wire::VsliceRange,
fuchsia_hardware_block_volume::wire::kMaxSliceRequests>
ranges;
static_assert(sizeof(decltype(ranges)::value_type) == sizeof(slice_region_t));
slice_region_t* ranges_ptr = reinterpret_cast<slice_region_t*>(ranges.data());
size_t range_count;
zx_status_t status = parent_volume_protocol_.QuerySlices(
request->start_slices.data(), request->start_slices.count(), ranges_ptr, std::size(ranges),
&range_count);
completer.Reply(status, ranges, range_count);
}
void BlockDevice::GetVolumeInfo(GetVolumeInfoCompleter::Sync& completer) {
if (!parent_volume_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, {}, {});
return;
}
fuchsia_hardware_block_volume::wire::VolumeManagerInfo manager_info;
static_assert(sizeof(manager_info) == sizeof(volume_manager_info_t));
fuchsia_hardware_block_volume::wire::VolumeInfo volume_info;
static_assert(sizeof(volume_info) == sizeof(volume_info_t));
zx_status_t status =
parent_volume_protocol_.GetInfo(reinterpret_cast<volume_manager_info_t*>(&manager_info),
reinterpret_cast<volume_info_t*>(&volume_info));
fidl::ObjectView<decltype(manager_info)> manager_info_view;
fidl::ObjectView<decltype(volume_info)> volume_info_view;
if (status == ZX_OK) {
manager_info_view = decltype(manager_info_view)::FromExternal(&manager_info);
volume_info_view = decltype(volume_info_view)::FromExternal(&volume_info);
}
completer.Reply(status, manager_info_view, volume_info_view);
}
void BlockDevice::Extend(ExtendRequestView request, ExtendCompleter::Sync& completer) {
if (!parent_volume_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
slice_extent_t extent = {
.offset = request->start_slice,
.length = request->slice_count,
};
completer.Reply(parent_volume_protocol_.Extend(&extent));
}
void BlockDevice::Shrink(ShrinkRequestView request, ShrinkCompleter::Sync& completer) {
if (!parent_volume_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
slice_extent_t extent = {
.offset = request->start_slice,
.length = request->slice_count,
};
completer.Reply(parent_volume_protocol_.Shrink(&extent));
}
void BlockDevice::Destroy(DestroyCompleter::Sync& completer) {
if (!parent_volume_protocol_.is_valid()) {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
completer.Reply(parent_volume_protocol_.Destroy());
}
zx_status_t BlockDevice::Bind(void* ctx, zx_device_t* dev) {
auto bdev = std::make_unique<BlockDevice>(dev);
// The Block Implementation Protocol is required.
if (!bdev->parent_protocol_.is_valid()) {
zxlogf(ERROR, "block device: does not support block protocol");
return ZX_ERR_NOT_SUPPORTED;
}
bdev->parent_protocol_.Query(&bdev->info_, &bdev->parent_op_size_);
if (bdev->info_.max_transfer_size < bdev->info_.block_size) {
zxlogf(ERROR, "block device: has smaller max xfer (0x%x) than block size (0x%x)",
bdev->info_.max_transfer_size, bdev->info_.block_size);
return ZX_ERR_NOT_SUPPORTED;
}
bdev->io_op_ = std::make_unique<uint8_t[]>(bdev->OpSize());
size_t block_size = bdev->info_.block_size;
if ((block_size < 512) || (block_size & (block_size - 1))) {
zxlogf(ERROR, "block device: invalid block size: %zu", block_size);
return ZX_ERR_NOT_SUPPORTED;
}
// Check to see if we have a ZBI partition map.
uint8_t buffer[METADATA_PARTITION_MAP_MAX];
size_t actual;
zx_status_t status =
device_get_metadata(dev, DEVICE_METADATA_PARTITION_MAP, buffer, sizeof(buffer), &actual);
if (status == ZX_OK && actual >= sizeof(zbi_partition_map_t)) {
bdev->has_bootpart_ = true;
}
// We implement |ZX_PROTOCOL_BLOCK|, not |ZX_PROTOCOL_BLOCK_IMPL|. This is the
// "core driver" protocol for block device drivers.
status =
bdev->DdkAdd(ddk::DeviceAddArgs("block").forward_metadata(dev, DEVICE_METADATA_GPT_INFO));
if (status != ZX_OK) {
return status;
}
// The device has been added; we'll release it in blkdev_release.
[[maybe_unused]] auto r = bdev.release();
return ZX_OK;
}
static constexpr zx_driver_ops_t block_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = &BlockDevice::Bind;
return ops;
}();
ZIRCON_DRIVER(block, block_driver_ops, "zircon", "0.1");