blob: 0b3e0f3937b32000454105c1cf48fa1a02f14040 [file] [log] [blame]
// 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 <fuchsia/hardware/block/c/banjo.h>
#include <fuchsia/hardware/block/cpp/banjo.h>
#include <lib/sync/completion.h>
#include <lib/virtio/backends/backend.h>
#include <lib/virtio/device.h>
#include <lib/virtio/ring.h>
#include <stdlib.h>
#include <zircon/compiler.h>
#include <atomic>
#include <memory>
#include <ddktl/device.h>
#include <virtio/block.h>
#include "src/lib/listnode/listnode.h"
namespace virtio {
struct block_txn_t {
block_op_t op;
block_impl_queue_callback completion_cb;
void* cookie;
struct vring_desc* desc;
size_t index;
list_node_t node;
zx_handle_t pmt;
class Ring;
class BlockDevice;
using DeviceType = ddk::Device<BlockDevice, ddk::GetProtocolable, ddk::GetSizable, ddk::Unbindable>;
class BlockDevice : public Device,
// Mixins for protocol device:
public DeviceType,
// Mixin for Block banjo protocol:
public ddk::BlockImplProtocol<BlockDevice, ddk::base_protocol> {
BlockDevice(zx_device_t* device, zx::bti bti, std::unique_ptr<Backend> backend);
virtual zx_status_t Init() override;
// DDKTL device hooks:
void DdkRelease();
void DdkUnbind(ddk::UnbindTxn txn);
zx_off_t DdkGetSize() const { return config_.capacity * config_.blk_size; }
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
virtual void IrqRingUpdate() override;
virtual void IrqConfigChange() override;
uint32_t GetBlockSize() const { return config_.blk_size; }
uint64_t GetBlockCount() const { return config_.capacity; }
const char* tag() const override { return "virtio-blk"; }
// DDKTL Block protocol banjo functions:
void BlockImplQuery(block_info_t* bi, size_t* bopsz);
void BlockImplQueue(block_op_t* bop, block_impl_queue_callback completion_cb, void* cookie);
void SignalWorker(block_txn_t* txn);
void WorkerThread();
void FlushPendingTxns();
void CleanupPendingTxns();
zx_status_t QueueTxn(block_txn_t* txn, uint32_t type, size_t bytes, zx_paddr_t* pages,
size_t pagecount, uint16_t* idx);
void txn_complete(block_txn_t* txn, zx_status_t status);
// The main virtio ring.
Ring vring_ = {this};
// Lock to be used around Ring::AllocDescChain and FreeDesc.
// TODO: Move this into Ring class once it's certain that other users of the class are okay with
// it.
fbl::Mutex ring_lock_;
static const uint16_t ring_size = 128; // 128 matches legacy pci.
// Saved block device configuration out of the pci config BAR.
virtio_blk_config_t config_ = {};
// A queue of block request/responses.
static const size_t blk_req_count = 32;
io_buffer_t blk_req_buf_;
virtio_blk_req_t* blk_req_ = nullptr;
zx_paddr_t blk_res_pa_ = 0;
uint8_t* blk_res_ = nullptr;
uint32_t blk_req_bitmap_ = 0;
static_assert(blk_req_count <= sizeof(blk_req_bitmap_) * CHAR_BIT, "");
size_t alloc_blk_req() {
size_t i = 0;
if (blk_req_bitmap_ != 0) {
i = sizeof(blk_req_bitmap_) * CHAR_BIT - __builtin_clz(blk_req_bitmap_);
if (i < blk_req_count) {
blk_req_bitmap_ |= (1 << i);
return i;
void free_blk_req(size_t i) { blk_req_bitmap_ &= ~(1 << i); }
// Pending txns and completion signal.
fbl::Mutex txn_lock_;
list_node pending_txn_list_ = LIST_INITIAL_VALUE(pending_txn_list_);
sync_completion_t txn_signal_;
// Worker state.
thrd_t worker_thread_;
list_node worker_txn_list_ = LIST_INITIAL_VALUE(worker_txn_list_);
sync_completion_t worker_signal_;
std::atomic_bool worker_shutdown_ = false;
} // namespace virtio