blob: 400e6f522092b568cddc1ed71bd7ddc0b1509029 [file]
// Copyright 2016 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/virtio/block.h"
#include <fidl/fuchsia.storage.block/cpp/fidl.h>
#include <inttypes.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/defer.h>
#include <lib/virtio/driver_utils.h>
#include <lib/zircon-internal/align.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <zircon/compiler.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include "src/devices/block/lib/common/include/common.h"
#include "src/storage/lib/block_server/block_server.h"
#define LOCAL_TRACE 0
namespace virtio {
namespace {
// Cache some page size calculations that are used frequently.
const uint32_t kPageSize = zx_system_get_page_size();
const uint32_t kPageMask = kPageSize - 1;
const uint32_t kMaxMaxXfer = (MAX_SCATTER - 1) * kPageSize;
// See 5.2.2 in the virtio spec
const uint16_t kVirtioBlkRequestQueueIndex = 0;
uint32_t VirtioRequestType(const block_server::Operation& operation) {
switch (operation.tag) {
case block_server::Operation::Tag::Read:
return VIRTIO_BLK_T_IN;
case block_server::Operation::Tag::Write:
return VIRTIO_BLK_T_OUT;
case block_server::Operation::Tag::Trim:
return VIRTIO_BLK_T_DISCARD;
case block_server::Operation::Tag::Flush:
return VIRTIO_BLK_T_FLUSH;
case block_server::Operation::Tag::CloseVmo:
case block_server::Operation::Tag::StartDecompressedRead:
case block_server::Operation::Tag::ContinueDecompressedRead:
__UNREACHABLE;
}
}
} // namespace
void BlockDevice::CompleteTxn(block_txn_t* transaction, zx_status_t status) {
fdf::trace("Complete txn {} {}", static_cast<const void*>(transaction),
zx_status_get_string(status));
if (transaction->pmt != ZX_HANDLE_INVALID) {
zx_pmt_unpin(transaction->pmt);
transaction->pmt = ZX_HANDLE_INVALID;
}
{
std::lock_guard<std::mutex> lock(watchdog_lock_);
blk_req_start_timestamps_[transaction->req_index] = zx::time::infinite();
}
// Save the request ID before we release the transaction's resources, because for block server
// requests, the req_index is used to reserve a slot in our pool for txn, so once we free that,
// txn could be reused.
uint64_t request_id = transaction->request;
{
fbl::AutoLock lock(&txn_lock_);
// NB: req_index might be invalid (>= kBlkReqCount) if transaction comes from Banjo but never
// even made it as far as allocating a req_index. That's tolerated by FreeBlkReqLocked.
FreeBlkReqLocked(transaction->req_index);
if (transaction->discard_req_index) {
FreeBlkReqLocked(*transaction->discard_req_index);
}
list_delete(&transaction->node);
}
txn_cond_.Broadcast();
std::lock_guard lock(block_server_lock_);
if (block_server_) {
block_server_->SendReply(request_id, zx::make_result(status));
}
}
uint32_t BlockDevice::GetMaxTransferSize() const {
const uint32_t max_transfer_size = static_cast<uint32_t>(kPageSize * (kRingSize - 2));
// Limit max transfer to our worst case scatter list size.
return std::min(max_transfer_size, kMaxMaxXfer);
}
fuchsia_storage_block::wire::DeviceFlag BlockDevice::GetFlags() const {
fuchsia_storage_block::wire::DeviceFlag flags = {};
if (supports_discard_) {
flags |= fuchsia_storage_block::wire::DeviceFlag::kTrimSupport;
}
return flags;
}
void BlockDevice::OnRequests(std::span<block_server::Request> requests) {
for (auto& request : requests) {
if (zx_status_t status = block_server::CheckIoRange(request, config_.capacity);
status != ZX_OK) {
fdf::warn("Invalid request range.");
std::lock_guard lock(block_server_lock_);
ZX_DEBUG_ASSERT(block_server_);
if (block_server_) {
block_server_->SendReply(request.request_id, zx::make_result(status));
}
continue;
}
zx::result status = SubmitBlockServerRequest(request);
if (status.is_error()) {
fdf::warn("Failed to submit request: {}", status);
std::lock_guard lock(block_server_lock_);
if (block_server_) {
block_server_->SendReply(request.request_id, status.take_error());
}
}
}
}
void BlockDevice::ServeRequests(fidl::ServerEnd<fuchsia_storage_block::Block> server_end) {
std::lock_guard lock(block_server_lock_);
if (block_server_) {
block_server_->Serve(std::move(server_end));
}
}
BlockDevice::BlockDevice(zx::bti bti, std::unique_ptr<Backend> backend, fdf::Logger& logger)
: virtio::Device(std::move(bti), std::move(backend)), logger_(logger) {
for (auto& time : blk_req_start_timestamps_) {
time = zx::time::infinite();
}
}
zx_status_t BlockDevice::Init() {
DeviceReset();
CopyDeviceConfig(&config_, sizeof(config_));
// TODO(cja): The blk_size provided in the device configuration is only
// populated if a specific feature bit has been negotiated during
// initialization, otherwise it is 0, at least in Virtio 0.9.5. Use 512
// as a default as a stopgap for now until proper feature negotiation
// is supported.
if (config_.blk_size == 0)
config_.blk_size = 512;
fdf::debug("capacity {:#x}", config_.capacity);
fdf::debug("size_max {:#x}", config_.size_max);
fdf::debug("seg_max {:#x}", config_.seg_max);
fdf::debug("blk_size {:#x}", config_.blk_size);
DriverStatusAck();
uint64_t features = DeviceFeaturesSupported();
if (!(features & VIRTIO_F_VERSION_1)) {
// Declaring non-support until there is a need in the future.
fdf::error("Legacy virtio interface is not supported by this driver");
return ZX_ERR_NOT_SUPPORTED;
}
if (features & VIRTIO_BLK_F_DISCARD) {
fdf::info("virtio device supports discard");
supports_discard_ = true;
}
if (features & VIRTIO_BLK_F_BARRIER) {
fdf::info("virtio device supports barriers");
supports_barriers_ = true;
}
features &= (VIRTIO_F_VERSION_1 | VIRTIO_BLK_F_DISCARD);
DriverFeaturesAck(features);
if (zx_status_t status = DeviceStatusFeaturesOk(); status != ZX_OK) {
fdf::error("Feature negotiation failed: {}", zx_status_get_string(status));
return status;
}
// Allocate the main vring.
auto err = vring_.Init(kVirtioBlkRequestQueueIndex, kRingSize);
if (err < 0) {
fdf::error("failed to allocate vring");
return err;
}
// Allocate a queue of block requests.
size_t size = sizeof(virtio_blk_req_t) * kBlkReqCount + sizeof(uint8_t) * kBlkReqCount;
auto buffer_factory = dma_buffer::CreateBufferFactory();
const size_t buffer_size = fbl::round_up(size, zx_system_get_page_size());
zx_status_t status = buffer_factory->CreateContiguous(
bti_, buffer_size, 0, dma_buffer::CacheOptions::kEnabled, &blk_req_buf_);
if (status != ZX_OK) {
fdf::error("cannot alloc blk_req buffers: {}", zx_status_get_string(status));
return status;
}
blk_req_ = static_cast<virtio_blk_req_t*>(blk_req_buf_->virt());
fdf::trace("allocated blk request at {}, physical address {:#x}",
static_cast<const void*>(blk_req_), blk_req_buf_->phys());
// Responses are 32 words at the end of the allocated block.
blk_res_pa_ = blk_req_buf_->phys() + sizeof(virtio_blk_req_t) * kBlkReqCount;
blk_res_ = reinterpret_cast<uint8_t*>(
(reinterpret_cast<uintptr_t>(blk_req_) + sizeof(virtio_blk_req_t) * kBlkReqCount));
fdf::trace("allocated blk responses at {}, physical address {:#x}",
static_cast<const void*>(blk_res_), blk_res_pa_);
StartIrqThread();
DriverStatusOk();
{
std::lock_guard lock(block_server_lock_);
block_server_.emplace(
block_server::PartitionInfo{
.device_flags = static_cast<uint32_t>(GetFlags()),
.block_count = GetBlockCount(),
.block_size = GetBlockSize(),
.max_transfer_size = GetMaxTransferSize(),
},
this);
}
auto watchdog_thread_entry = [](void* ctx) {
auto bd = static_cast<BlockDevice*>(ctx);
bd->WatchdogThread();
return ZX_OK;
};
int ret = thrd_create_with_name(&watchdog_thread_, watchdog_thread_entry, this,
"virtio-block-watchdog");
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void BlockDevice::Release() {
watchdog_shutdown_.store(true);
sync_completion_signal(&watchdog_signal_);
thrd_join(watchdog_thread_, nullptr);
{
fbl::AutoLock lock(&txn_lock_);
shutdown_ = true;
}
txn_cond_.Broadcast();
// After `shutdown_ = true`, no more transactions will be created, but we need to clean up those
// which are already in-flight so we don't block shutdown.
CleanUpPendingTxns();
{
std::lock_guard lock(block_server_lock_);
block_server_.reset();
}
virtio::Device::Release();
}
struct vring_desc* BlockDevice::FreeDescChainLocked(uint16_t index) {
struct vring_desc* desc = vring_.DescFromIndex(index);
auto head_desc = desc; // Save the first element.
{
for (;;) {
std::optional<uint16_t> next;
if (fdf::Logger::GlobalInstance()->GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(desc);
}
if (desc->flags & VRING_DESC_F_NEXT) {
next = desc->next;
}
vring_.FreeDesc(index);
if (!next) {
// End of chain
break;
}
index = *next;
desc = vring_.DescFromIndex(index);
}
}
return head_desc;
}
void BlockDevice::IrqRingUpdate() {
// Parse our descriptor chain and add back to the free queue.
auto free_chain = [this](vring_used_elem* used_elem) {
std::optional<uint8_t> status;
block_txn_t* txn = nullptr;
{
fbl::AutoLock lock(&txn_lock_);
struct vring_desc* head_desc;
{
std::lock_guard<std::mutex> lock2(ring_lock_);
head_desc = FreeDescChainLocked(static_cast<uint16_t>(used_elem->id));
}
// Search our pending txn list to see if this completes it.
list_for_every_entry (&pending_txn_list_, txn, block_txn_t, node) {
if (txn->desc == head_desc) {
fdf::trace("completes txn {}", static_cast<const void*>(txn));
status = blk_res_[txn->req_index];
// NB: We can't free the transaction's resources until we complete it, because the
// req_index is used to allocate requests out of the pool.
break;
}
}
}
if (status) {
zx_status_t zx_status = ZX_ERR_IO;
switch (*status) {
case VIRTIO_BLK_S_OK:
zx_status = ZX_OK;
break;
case VIRTIO_BLK_S_IOERR:
break;
case VIRTIO_BLK_S_UNSUPP:
zx_status = ZX_ERR_NOT_SUPPORTED;
}
CompleteTxn(txn, zx_status);
}
};
// Tell the ring to find free chains and hand it back to our lambda.
vring_.IrqRingUpdate(free_chain);
}
void BlockDevice::IrqConfigChange() {}
void BlockDevice::QueueTxn(block_txn_t* txn, RequestContext context) {
ZX_DEBUG_ASSERT(txn);
uint32_t type = VirtioRequestType(txn->operation);
txn->req_index = context.req_index();
txn->discard_req_index = context.discard_req_index();
txn->desc = context.desc();
txn->pmt = context.pmt();
auto req = &blk_req_[txn->req_index];
req->type = type;
req->ioprio = 0;
if (txn->operation.tag == block_server::Operation::Tag::Write &&
txn->operation.write.options.flags.is_pre_barrier()) {
// The block server library should only send barriers down if we advertise support for them.
ZX_DEBUG_ASSERT(supports_barriers_);
req->type |= VIRTIO_BLK_T_BARRIER;
}
if (req->type == VIRTIO_BLK_T_FLUSH) {
req->sector = 0;
} else {
if (txn->operation.tag == block_server::Operation::Tag::Read) {
req->sector = txn->operation.read.device_block_offset;
} else if (txn->operation.tag == block_server::Operation::Tag::Write) {
req->sector = txn->operation.write.device_block_offset;
}
}
fdf::trace("blk_req type {} ioprio {} sector {}", req->type, req->ioprio, req->sector);
if (type == VIRTIO_BLK_T_DISCARD) {
// NOTE: if we decide to later send multiple virtio_blk_discard_write_zeroes at once, we must
// respect the max_discard_seg configuration of the device.
static_assert(sizeof(virtio_blk_discard_write_zeroes_t) <= sizeof(virtio_blk_req_t));
virtio_blk_discard_write_zeroes_t* req =
reinterpret_cast<virtio_blk_discard_write_zeroes_t*>(&blk_req_[*txn->discard_req_index]);
req->sector = txn->operation.trim.device_block_offset;
req->num_sectors = txn->operation.trim.block_count;
req->flags = 0;
fdf::trace("blk_dwz_req sector {} num_sectors {}", req->sector, req->num_sectors);
}
fdf::trace("page count {}", context.num_pages());
// Set up the head descriptor.
struct vring_desc* desc = context.desc();
desc->addr = blk_req_buf_->phys() + txn->req_index * sizeof(virtio_blk_req_t);
desc->len = sizeof(virtio_blk_req_t);
desc->flags = VRING_DESC_F_NEXT;
if (fdf::Logger::GlobalInstance()->GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(txn->desc);
}
size_t bytes = type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_OUT
? txn->operation.read.block_count * config_.blk_size
: 0;
for (size_t n = 0; n < context.num_pages(); n++) {
desc = vring_.DescFromIndex(desc->next);
desc->addr = context.pages()[n]; // |pages| are all page-aligned addresses.
desc->len = static_cast<uint32_t>((bytes > kPageSize) ? kPageSize : bytes);
if (n == 0) {
// First entry may not be page aligned.
size_t page0_offset = txn->operation.read.vmo_offset & kPageMask;
// Adjust starting address.
desc->addr += page0_offset;
// Trim length if necessary.
size_t max = kPageSize - page0_offset;
if (desc->len > max) {
desc->len = static_cast<uint32_t>(max);
}
}
desc->flags = VRING_DESC_F_NEXT;
fdf::trace("pa {:#x}, len {:#x}", desc->addr, desc->len);
// Mark buffer as write-only if its a block read.
if (type == VIRTIO_BLK_T_IN) {
desc->flags |= VRING_DESC_F_WRITE;
}
bytes -= desc->len;
}
assert(bytes == 0);
if (type == VIRTIO_BLK_T_DISCARD) {
desc = vring_.DescFromIndex(desc->next);
desc->addr = blk_req_buf_->phys() + *context.discard_req_index() * sizeof(virtio_blk_req_t);
desc->len = sizeof(virtio_blk_discard_write_zeroes_t);
desc->flags = VRING_DESC_F_NEXT;
if (fdf::Logger::GlobalInstance()->GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(desc);
}
}
// Set up the descriptor pointing to the response.
desc = vring_.DescFromIndex(desc->next);
desc->addr = blk_res_pa_ + context.req_index();
desc->len = 1;
desc->flags = VRING_DESC_F_WRITE;
if (fdf::Logger::GlobalInstance()->GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(desc);
}
{
std::lock_guard<std::mutex> lock(watchdog_lock_);
blk_req_start_timestamps_[context.req_index()] = zx::clock::get_monotonic();
}
fbl::AutoLock lock(&txn_lock_);
list_add_tail(&pending_txn_list_, &txn->node);
vring_.SubmitChain(context.desc_index());
vring_.Kick();
fdf::trace("Submitted txn {} (desc {})", static_cast<const void*>(txn), context.desc_index());
context.Release();
}
zx::result<zx_handle_t> BlockDevice::PinPages(zx_handle_t bti, zx_handle_t vmo,
uint64_t vmo_offset_bytes, uint32_t num_blocks,
std::array<zx_paddr_t, MAX_SCATTER>* pages,
size_t* num_pages) const {
uint64_t suboffset = vmo_offset_bytes & kPageMask;
uint64_t aligned_offset = vmo_offset_bytes & ~kPageMask;
size_t pin_size =
ZX_ROUNDUP(suboffset + (static_cast<uint64_t>(num_blocks * config_.blk_size)), kPageSize);
*num_pages = pin_size / kPageSize;
if (*num_pages > pages->size()) {
fdf::error("transaction too large");
return zx::error(ZX_ERR_INVALID_ARGS);
}
zx_handle_t pmt;
zx_status_t status = zx_bti_pin(bti, ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE, vmo, aligned_offset,
pin_size, pages->data(), *num_pages, &pmt);
if (status != ZX_OK) {
fdf::error("could not pin pages: {}", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(pmt);
}
zx::result<> BlockDevice::SubmitBlockServerRequest(const block_server::Request& request) {
uint32_t type = VirtioRequestType(request.operation);
uint64_t vmo_offset_bytes = 0;
uint32_t num_blocks = 0;
switch (request.operation.tag) {
case block_server::Operation::Tag::Read:
vmo_offset_bytes = request.operation.read.vmo_offset;
num_blocks = request.operation.read.block_count;
break;
case block_server::Operation::Tag::Write:
vmo_offset_bytes = request.operation.write.vmo_offset;
num_blocks = request.operation.write.block_count;
break;
case block_server::Operation::Tag::Trim:
num_blocks = request.operation.trim.block_count;
break;
case block_server::Operation::Tag::Flush:
break;
case block_server::Operation::Tag::CloseVmo:
case block_server::Operation::Tag::StartDecompressedRead:
case block_server::Operation::Tag::ContinueDecompressedRead:
__UNREACHABLE;
}
std::array<zx_paddr_t, MAX_SCATTER> pages;
zx::result<RequestContext> context =
AllocateRequestContext(type, request.vmo->get(), vmo_offset_bytes, num_blocks, &pages);
if (context.is_error()) {
return context.take_error();
}
block_txn_t* txn = &block_server_request_pool_[context->req_index()];
txn->request = request.request_id;
txn->operation = request.operation;
txn->vmo = request.vmo;
// A flush operation should complete after any in-flight transactions, so wait for all
// pending txns to complete before submitting a flush txn. This is necessary because a virtio
// block device may service requests in any order.
if (type == VIRTIO_BLK_T_FLUSH) {
if (zx::result result = FlushPendingTxns(); result.is_error()) {
fbl::AutoLock lock(&txn_lock_);
std::lock_guard lock2(ring_lock_);
FreeRequestContext(*context);
return result.take_error();
}
}
QueueTxn(txn, std::move(*context));
// A flush operation should complete before any subsequent transactions. So, we wait
// for all pending transactions (including the flush) to complete before continuing.
// We don't need to check the result of this second FlushPendingTxns because the actual
// status is captured in the completion path (CompleteTxn).
if (type == VIRTIO_BLK_T_FLUSH) {
[[maybe_unused]] zx::result result = FlushPendingTxns();
}
return zx::ok();
}
BlockDevice::RequestContext::RequestContext(BlockDevice::RequestContext&& other) {
*this = std::move(other);
}
BlockDevice::RequestContext& BlockDevice::RequestContext::operator=(RequestContext&& other) {
if (this != &other) {
released_ = other.released_;
req_index_ = other.req_index_;
discard_req_index_ = other.discard_req_index_;
desc_index_ = other.desc_index_;
desc_ = other.desc_;
pages_ = other.pages_;
num_pages_ = other.num_pages_;
pmt_ = other.pmt_;
other.Release();
}
return *this;
}
BlockDevice::RequestContext::~RequestContext() {
// RAII-style cleanup isn't a good option, because the destructor would need to take locks (see
// BlockDevice::FreeRequestContext), which could cause difficult-to-spot deadlocks.
ZX_ASSERT_MSG(released_, "Did you forget to call Release/FreeRequestContext?");
}
void BlockDevice::FreeRequestContext(BlockDevice::RequestContext& context) {
if (context.pmt() != ZX_HANDLE_INVALID) {
zx_pmt_unpin(context.pmt());
}
if (context.desc() != nullptr) {
FreeDescChainLocked(context.desc_index());
}
if (context.req_index() < kBlkReqCount) {
FreeBlkReqLocked(context.req_index());
if (context.discard_req_index()) {
FreeBlkReqLocked(*context.discard_req_index());
}
}
context.Release();
txn_cond_.Broadcast();
}
void BlockDevice::RequestContext::Release() {
ZX_ASSERT_MSG(!released_, "Release called twice");
released_ = true;
}
// Thread safety: Disable thread safety analysis because TA doesn't understand std::unique_lock,
// which is required by std::condition_variable.
zx::result<BlockDevice::RequestContext> BlockDevice::AllocateRequestContext(
uint32_t type, zx_handle_t vmo, uint64_t vmo_offset_bytes, uint32_t num_blocks,
std::array<zx_paddr_t, MAX_SCATTER>* pages) TA_NO_THREAD_SAFETY_ANALYSIS {
for (;;) {
fbl::AutoLock lock(&txn_lock_);
if (shutdown_) {
return zx::error(ZX_ERR_IO_NOT_PRESENT);
}
zx::result<std::optional<RequestContext>> result;
{
std::unique_lock lock2(ring_lock_);
result = TryAllocateRequestContextLocked(type, vmo, vmo_offset_bytes, num_blocks, pages);
}
if (result.is_error()) {
fdf::error("failed to allocate virtio resources: {}", result);
return result.take_error();
}
if (result.value().has_value()) {
return zx::ok(std::move(*result).value());
}
// No resources; try again.
txn_cond_.Wait(&txn_lock_);
}
}
zx::result<std::optional<BlockDevice::RequestContext>> BlockDevice::TryAllocateRequestContextLocked(
uint32_t type, zx_handle_t vmo, uint64_t vmo_offset_bytes, uint32_t num_blocks,
std::array<zx_paddr_t, MAX_SCATTER>* pages) {
RequestContext out;
// Thread safety: TryAllocateRequestContextLocked requires the necessary locks.
auto cleanup = fit::defer([&]() TA_NO_THREAD_SAFETY_ANALYSIS { FreeRequestContext(out); });
std::optional<size_t> idx = AllocateBlkReqLocked();
if (!idx) {
fdf::trace("too many block requests queued!");
return zx::ok(std::nullopt);
}
out.set_req_index(*idx);
if (type == VIRTIO_BLK_T_DISCARD) {
// A second descriptor needs to be allocated for discard requests.
idx = AllocateBlkReqLocked();
if (!idx) {
fdf::trace("too many block requests queued!");
return zx::ok(std::nullopt);
}
out.set_discard_req_index(*idx);
}
if (type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_OUT) {
size_t num_pages;
zx::result result = PinPages(bti_.get(), vmo, vmo_offset_bytes, num_blocks, pages, &num_pages);
if (result.is_error()) {
return result.take_error();
}
out.set_pages(pages->data(), num_pages);
out.set_pmt(result.value());
}
uint16_t num_descriptors =
(type == VIRTIO_BLK_T_DISCARD ? 3u : 2u) + static_cast<uint16_t>(out.num_pages());
uint16_t desc_index;
struct vring_desc* desc = nullptr;
desc = vring_.AllocDescChain(num_descriptors, &desc_index);
if (!desc) {
fdf::trace("failed to allocate descriptor chain of length {}", 2u + out.num_pages());
return zx::ok(std::nullopt);
}
out.set_desc(desc, desc_index);
cleanup.cancel();
return zx::ok(std::move(out));
}
void BlockDevice::WatchdogThread() {
for (;;) {
sync_completion_wait(&watchdog_signal_, kWatchdogInterval.get());
if (watchdog_shutdown_.load()) {
return;
}
zx::time now = zx::clock::get_monotonic();
{
std::lock_guard<std::mutex> lock(watchdog_lock_);
int idx = 0;
for (const auto& start_time : blk_req_start_timestamps_) {
if (now - kWatchdogInterval >= start_time) {
// Round down to the interval
zx::duration latency = ((now - start_time) / kWatchdogInterval) * kWatchdogInterval;
// LINT.IfChange(watchdog_tefmo)
fdf::warn("txn {} has not completed after {}s!", idx, latency.to_secs());
// LINT.ThenChange(/tools/testing/tefmocheck/string_in_log_check.go:watchdog_tefmo)
}
idx += 1;
}
}
}
}
zx::result<> BlockDevice::FlushPendingTxns() {
fbl::AutoLock lock(&txn_lock_);
while (!shutdown_ && !list_is_empty(&pending_txn_list_)) {
txn_cond_.Wait(&txn_lock_);
}
if (shutdown_) {
return zx::error(ZX_ERR_IO_NOT_PRESENT);
}
return zx::ok();
}
void BlockDevice::CleanUpPendingTxns() {
// Virtio specification 3.3.1 Driver Requirements: Device Cleanup
// A driver MUST ensure a virtqueue isn’t live (by device reset) before removing exposed
// buffers.
DeviceReset();
block_txn_t* txn = nullptr;
block_txn_t* temp_entry = nullptr;
fbl::AutoLock lock(&txn_lock_);
list_for_every_entry_safe (&pending_txn_list_, txn, temp_entry, block_txn_t, node) {
FreeBlkReqLocked(txn->req_index);
if (txn->discard_req_index) {
FreeBlkReqLocked(*txn->discard_req_index);
}
list_delete(&txn->node);
CompleteTxn(txn, ZX_ERR_IO_NOT_PRESENT);
}
}
zx::result<> BlockDriver::Start(fdf::DriverContext context) {
auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming());
node_token_ = context.take_node_token();
zx::result device = CreateBlockDevice(*incoming);
if (device.is_error()) {
return device.take_error();
}
block_device_ = std::move(*device);
zx_status_t status = block_device_->Init();
if (status != ZX_OK) {
return zx::error(status);
}
auto [controller_client_end, controller_server_end] =
fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
node_controller_.Bind(std::move(controller_client_end));
fidl::Arena arena;
const auto args =
fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena).name(arena, name()).Build();
auto result =
fidl::WireCall(node().borrow())->AddChild(args, std::move(controller_server_end), {});
if (!result.ok()) {
fdf::error("Failed to add child: {}", result.status_string());
return zx::error(result.status());
}
if (zx::result result = outgoing()->AddService<fuchsia_hardware_block_volume::Service>(
fuchsia_hardware_block_volume::Service::InstanceHandler({
.volume =
[this](fidl::ServerEnd<fuchsia_storage_block::Block> server_end) {
block_device_->ServeRequests(std::move(server_end));
},
.token =
[this](fidl::ServerEnd<fuchsia_driver_token::NodeToken> server_end) {
fidl::BindServer(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(server_end), this);
},
}));
result.is_error()) {
fdf::error("Failed to add volume service instance: {}", result);
return result.take_error();
}
return zx::ok();
}
zx::result<std::unique_ptr<BlockDevice>> BlockDriver::CreateBlockDevice(
const fdf::Namespace& incoming) {
zx::result<fidl::ClientEnd<fuchsia_hardware_pci::Device>> pci_client_result =
incoming.Connect<fuchsia_hardware_pci::Service::Device>();
if (pci_client_result.is_error()) {
fdf::error("Failed to get pci client: {}", pci_client_result);
return pci_client_result.take_error();
}
zx::result<std::pair<zx::bti, std::unique_ptr<virtio::Backend>>> bti_and_backend_result =
virtio::GetBtiAndBackend(std::move(pci_client_result).value());
if (!bti_and_backend_result.is_ok()) {
fdf::error("GetBtiAndBackend failed: {}", bti_and_backend_result);
return bti_and_backend_result.take_error();
}
auto [bti, backend] = std::move(bti_and_backend_result).value();
return zx::ok(std::make_unique<BlockDevice>(std::move(bti), std::move(backend), logger()));
}
void BlockDriver::Stop(fdf::StopCompleter completer) {
if (block_device_) {
block_device_->Release();
}
completer(zx::ok());
}
void BlockDriver::Get(GetCompleter::Sync& completer) {
zx::event token = node_token();
if (token.is_valid()) {
completer.Reply(zx::ok(std::move(token)));
} else {
completer.Reply(zx::error(ZX_ERR_NOT_FOUND));
}
}
} // namespace virtio