blob: 055cef8c8f36411129a091552f90427028bf6ee1 [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.
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <atomic>
#include <new>
#include <utility>
#include <ddk/protocol/block.h>
#include <ddktl/device.h>
#include <ddktl/protocol/block.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <lib/fzl/fifo.h>
#include <lib/sync/completion.h>
#include <lib/zx/vmo.h>
#include <zircon/device/block.h>
#include <zircon/thread_annotations.h>
#include <zircon/types.h>
#include "txn-group.h"
// Represents the mapping of "vmoid --> VMO"
class IoBuffer : public fbl::WAVLTreeContainable<fbl::RefPtr<IoBuffer>>,
public fbl::RefCounted<IoBuffer> {
public:
vmoid_t GetKey() const { return vmoid_; }
// TODO(smklein): This function is currently labelled 'hack' since we have
// no way to ensure that the size of the VMO won't change in between
// checking it and using it. This will require a mechanism to "pin" VMO pages.
// The units of length and vmo_offset is bytes.
zx_status_t ValidateVmoHack(uint64_t length, uint64_t vmo_offset);
zx_handle_t vmo() const { return io_vmo_.get(); }
IoBuffer(zx::vmo vmo, vmoid_t vmoid);
~IoBuffer();
private:
friend struct TypeWAVLTraits;
DISALLOW_COPY_ASSIGN_AND_MOVE(IoBuffer);
const zx::vmo io_vmo_;
const vmoid_t vmoid_;
};
class BlockServer;
class BlockMessage;
// A single unit of work transmitted to the underlying block layer.
// BlockMessage contains a block_op_t, which is dynamically sized. Therefore, it implements its
// own allocator that takes block_op_size.
class BlockMessage final : public fbl::DoublyLinkedListable<BlockMessage*> {
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(BlockMessage);
BlockMessage() { }
// Overloaded new operator allows variable-sized allocation to match block op size.
void* operator new(size_t size) = delete;
void* operator new(size_t size, size_t block_op_size) {
return calloc(1, size + block_op_size - sizeof(block_op_t));
}
void operator delete(void* msg) { free(msg); }
// Allocate a new, uninitialized BlockMessage whose block_op begins in a memory region that
// is block_op_size bytes long.
static zx_status_t Create(size_t block_op_size, fbl::unique_ptr<BlockMessage>* out);
// Initialize the contents of this from the supplied args. block_op op_ is cleared.
void Init(fbl::RefPtr<IoBuffer> iobuf, BlockServer* server, block_fifo_request_t* req);
// End the transaction specified by reqid and group, and release iobuf.
// BlockMessage can be reused with another call to Init().
void Complete(zx_status_t status);
block_op_t* Op() { return &op_; }
private:
fbl::RefPtr<IoBuffer> iobuf_;
BlockServer* server_;
reqid_t reqid_;
groupid_t group_;
size_t op_size_;
// Must be at the end of structure.
union {
block_op_t op_;
uint8_t _op_raw_[1]; // Extra space for underlying block_op.
};
};
using BlockMessageQueue = fbl::DoublyLinkedList<BlockMessage*>;
class BlockServer {
public:
// Creates a new BlockServer.
static zx_status_t Create(
ddk::BlockProtocolClient* bp,
fzl::fifo<block_fifo_request_t, block_fifo_response_t>* fifo_out,
BlockServer** out);
// Starts the BlockServer using the current thread
zx_status_t Serve() TA_EXCL(server_lock_);
zx_status_t AttachVmo(zx::vmo vmo, vmoid_t* out) TA_EXCL(server_lock_);
// Updates the total number of pending txns, possibly signals
// the queue-draining thread to wake up if they are waiting
// for all pending operations to complete.
//
// Should only be called for transactions which have been placed
// on (and removed from) in_queue_.
void TxnEnd();
// Wrapper around "Completed Transaction", as a convenience
// both both one-shot and group-based transactions.
//
// (If appropriate) tells the client that their operation is done.
void TxnComplete(zx_status_t status, reqid_t reqid, groupid_t group);
void ShutDown();
~BlockServer();
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(BlockServer);
BlockServer(ddk::BlockProtocolClient* bp);
// Helper for processing a single message read from the FIFO.
void ProcessRequest(block_fifo_request_t* request);
zx_status_t ProcessReadWriteRequest(block_fifo_request_t* request) TA_EXCL(server_lock_);
zx_status_t ProcessCloseVmoRequest(block_fifo_request_t* request) TA_EXCL(server_lock_);
zx_status_t ProcessFlushRequest(block_fifo_request_t* request);
zx_status_t ProcessTrimRequest(block_fifo_request_t* request);
// Helper for the server to react to a signal that a barrier
// operation has completed. Unsets the local "waiting for barrier"
// signal, and enqueues any further operations that might be
// pending.
void BarrierComplete();
// Functions that read from the fifo and invoke the queue drainer.
// Should not be invoked concurrently.
zx_status_t Read(block_fifo_request_t* requests, size_t* count);
void TerminateQueue();
// Attempts to enqueue all operations on the |in_queue_|. Stops
// when either the queue is empty, or a BARRIER_BEFORE is reached and
// operations are in-flight.
void InQueueDrainer();
zx_status_t FindVmoIDLocked(vmoid_t* out) TA_REQ(server_lock_);
fzl::fifo<block_fifo_response_t, block_fifo_request_t> fifo_;
block_info_t info_;
ddk::BlockProtocolClient* bp_;
size_t block_op_size_;
// BARRIER_AFTER is implemented by sticking "BARRIER_BEFORE" on the
// next operation that arrives.
bool deferred_barrier_before_ = false;
BlockMessageQueue in_queue_;
std::atomic<size_t> pending_count_;
std::atomic<bool> barrier_in_progress_;
TransactionGroup groups_[MAX_TXN_GROUP_COUNT];
fbl::Mutex server_lock_;
fbl::WAVLTree<vmoid_t, fbl::RefPtr<IoBuffer>> tree_ TA_GUARDED(server_lock_);
vmoid_t last_id_ TA_GUARDED(server_lock_);
};