blob: 8533272ba4766712549d3f554d549cb40ce21f8a [file] [log] [blame]
// Copyright 2019 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 "pipe.h"
#include <ddk/debug.h>
#include <ddk/trace/event.h>
#include <fbl/auto_lock.h>
#include <fuchsia/hardware/goldfish/pipe/c/fidl.h>
#include <lib/fidl-utils/bind.h>
#include <lib/zx/bti.h>
namespace goldfish {
namespace {
constexpr size_t DEFAULT_BUFFER_SIZE = 8192;
constexpr zx_signals_t SIGNALS =
fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE | fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE;
constexpr uint32_t kConcurrencyCap = 64;
} // namespace
void vLog(bool is_error, const char* prefix1, const char* prefix2, const char* format,
va_list args) {
va_list args2;
va_copy(args2, args);
size_t buffer_bytes = vsnprintf(nullptr, 0, format, args) + 1;
std::unique_ptr<char[]> buffer(new char[buffer_bytes]);
size_t buffer_bytes_2 = vsnprintf(buffer.get(), buffer_bytes, format, args2) + 1;
(void)buffer_bytes_2;
// sanity check; should match so go ahead and assert that it does.
ZX_DEBUG_ASSERT(buffer_bytes == buffer_bytes_2);
va_end(args2);
if (is_error) {
zxlogf(ERROR, "[%s %s] %s\n", prefix1, prefix2, buffer.get());
} else {
zxlogf(TRACE, "[%s %s] %s\n", prefix1, prefix2, buffer.get());
}
}
const fuchsia_hardware_goldfish_pipe_Pipe_ops_t Pipe::kOps = {
fidl::Binder<Pipe>::BindMember<&Pipe::SetBufferSize>,
fidl::Binder<Pipe>::BindMember<&Pipe::SetEvent>,
fidl::Binder<Pipe>::BindMember<&Pipe::GetBuffer>,
fidl::Binder<Pipe>::BindMember<&Pipe::Read>,
fidl::Binder<Pipe>::BindMember<&Pipe::Write>,
fidl::Binder<Pipe>::BindMember<&Pipe::Call>,
};
Pipe::Pipe(zx_device_t* parent) : FidlServer("GoldfishPipe", kConcurrencyCap), pipe_(parent) {}
Pipe::~Pipe() {
fbl::AutoLock lock(&lock_);
if (id_) {
if (cmd_buffer_.is_valid()) {
auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
buffer->id = id_;
buffer->cmd = PIPE_CMD_CODE_CLOSE;
buffer->status = PIPE_ERROR_INVAL;
pipe_.Exec(id_);
ZX_DEBUG_ASSERT(!buffer->status);
}
pipe_.Destroy(id_);
}
}
void Pipe::Init() {
fbl::AutoLock lock(&lock_);
if (!pipe_.is_valid()) {
FailAsync(ZX_ERR_BAD_STATE, "Pipe::Pipe() no pipe protocol");
return;
}
zx_status_t status = pipe_.GetBti(&bti_);
if (status != ZX_OK) {
FailAsync(status, "Pipe::Pipe() GetBti failed");
return;
}
status = SetBufferSizeLocked(DEFAULT_BUFFER_SIZE);
if (status != ZX_OK) {
FailAsync(status, "Pipe::Pipe() failed to set initial buffer size");
return;
}
status = zx::event::create(0, &event_);
if (status != ZX_OK) {
FailAsync(status, "Pipe::Pipe() failed to create event");
return;
}
status = event_.signal(0, SIGNALS);
ZX_ASSERT(status == ZX_OK);
zx::vmo vmo;
goldfish_pipe_signal_value_t signal_cb = {Pipe::OnSignal, this};
status = pipe_.Create(&signal_cb, &id_, &vmo);
if (status != ZX_OK) {
FailAsync(status, "Pipe::Pipe() failed to create pipe");
return;
}
status = cmd_buffer_.InitVmo(bti_.get(), vmo.get(), 0, IO_BUFFER_RW);
if (status != ZX_OK) {
FailAsync(status, "Pipe::Pipe() io_buffer_init_vmo failed");
return;
}
auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
buffer->id = id_;
buffer->cmd = PIPE_CMD_CODE_OPEN;
buffer->status = PIPE_ERROR_INVAL;
pipe_.Open(id_);
if (buffer->status) {
FailAsync(ZX_ERR_INTERNAL, "Pipe::Pipe() failed to open pipe");
cmd_buffer_.release();
}
}
zx_status_t Pipe::SetBufferSize(uint64_t size, fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Pipe::SetBufferSize", "size", size);
BindingType::Txn::RecognizeTxn(txn);
fbl::AutoLock lock(&lock_);
zx_status_t status = SetBufferSizeLocked(size);
if (status != ZX_OK) {
LogError("Pipe::SetBufferSize() failed to create buffer: %lu", size);
return status;
}
return fuchsia_hardware_goldfish_pipe_PipeSetBufferSize_reply(txn, status);
}
zx_status_t Pipe::SetEvent(zx_handle_t event_handle) {
TRACE_DURATION("gfx", "Pipe::SetEvent");
zx::event event(event_handle);
if (!event.is_valid()) {
LogError("Pipe::SetEvent() invalid event");
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&lock_);
zx_handle_t observed = 0;
zx_status_t status = event_.wait_one(SIGNALS, zx::time::infinite_past(), &observed);
if (status != ZX_OK) {
LogError("Pipe::SetEvent() failed to transfer observed signals: %d", status);
return status;
}
event_ = std::move(event);
status = event_.signal(SIGNALS, observed & SIGNALS);
ZX_ASSERT(status == ZX_OK);
return ZX_OK;
}
zx_status_t Pipe::GetBuffer(fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Pipe::GetBuffer");
BindingType::Txn::RecognizeTxn(txn);
fbl::AutoLock lock(&lock_);
zx::vmo vmo;
zx_status_t status = buffer_.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
LogError("Pipe::GetBuffer() zx_vmo_duplicate failed: %d", status);
return status;
}
return fuchsia_hardware_goldfish_pipe_PipeGetBuffer_reply(txn, ZX_OK, vmo.release());
}
zx_status_t Pipe::Read(size_t count, zx_off_t offset, fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Pipe::Read", "count", count);
BindingType::Txn::RecognizeTxn(txn);
fbl::AutoLock lock(&lock_);
if ((offset + count) > buffer_.size || (offset + count) < offset) {
return ZX_ERR_INVALID_ARGS;
}
size_t actual;
zx_status_t status = TransferLocked(PIPE_CMD_CODE_READ, PIPE_CMD_CODE_WAKE_ON_READ,
fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE,
buffer_.phys + offset, count, 0, 0, &actual);
return fuchsia_hardware_goldfish_pipe_PipeRead_reply(txn, status, actual);
}
zx_status_t Pipe::Write(size_t count, zx_off_t offset, fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Pipe::Write", "count", count);
BindingType::Txn::RecognizeTxn(txn);
fbl::AutoLock lock(&lock_);
if ((offset + count) > buffer_.size || (offset + count) < offset) {
return ZX_ERR_INVALID_ARGS;
}
size_t actual;
zx_status_t status = TransferLocked(PIPE_CMD_CODE_WRITE, PIPE_CMD_CODE_WAKE_ON_WRITE,
fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE,
buffer_.phys + offset, count, 0, 0, &actual);
return fuchsia_hardware_goldfish_pipe_PipeWrite_reply(txn, status, actual);
}
zx_status_t Pipe::Call(size_t count, zx_off_t offset, size_t read_count, zx_off_t read_offset,
fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Pipe::Call", "count", count, "read_count", read_count);
BindingType::Txn::RecognizeTxn(txn);
fbl::AutoLock lock(&lock_);
if ((offset + count) > buffer_.size || (offset + count) < offset) {
return ZX_ERR_INVALID_ARGS;
}
if ((read_offset + read_count) > buffer_.size || (read_offset + read_count) < read_offset) {
return ZX_ERR_INVALID_ARGS;
}
size_t remaining = count;
size_t remaining_read = read_count;
int32_t cmd = PIPE_CMD_CODE_WRITE;
zx_paddr_t read_paddr = 0;
if (read_count) {
cmd = PIPE_CMD_CODE_CALL;
read_paddr = buffer_.phys + read_offset;
}
// Blocking write. This should always make progress or fail.
while (remaining) {
size_t actual;
zx_status_t status = TransferLocked(
cmd, PIPE_CMD_CODE_WAKE_ON_WRITE, fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE,
buffer_.phys + offset, remaining, read_paddr, read_count, &actual);
if (status == ZX_OK) {
// Calculate bytes written and bytes read. Adjust counts and offsets accordingly.
size_t actual_write = std::min(actual, remaining);
size_t actual_read = actual - actual_write;
remaining -= actual_write;
offset += actual_write;
remaining_read -= actual_read;
read_offset += actual_read;
continue;
}
if (status != ZX_ERR_SHOULD_WAIT) {
return fuchsia_hardware_goldfish_pipe_PipeCall_reply(txn, status, 0);
}
signal_cvar_.Wait(&lock_);
}
// Non-blocking read if no data has been read yet.
zx_status_t status = ZX_OK;
if (read_count && remaining_read == read_count) {
size_t actual = 0;
status = TransferLocked(PIPE_CMD_CODE_READ, PIPE_CMD_CODE_WAKE_ON_READ,
fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE,
buffer_.phys + read_offset, remaining_read, 0, 0, &actual);
if (status == ZX_OK) {
remaining_read -= actual;
}
}
size_t actual_read = read_count - remaining_read;
return fuchsia_hardware_goldfish_pipe_PipeCall_reply(txn, status, actual_read);
}
// This function can be trusted to complete fairly quickly. It will cause a
// VM exit but that should never block for a significant amount of time.
zx_status_t Pipe::TransferLocked(int32_t cmd, int32_t wake_cmd, zx_signals_t state_clr,
zx_paddr_t paddr, size_t count, zx_paddr_t read_paddr,
size_t read_count, size_t* actual) {
TRACE_DURATION("gfx", "Pipe::Transfer", "count", count, "read_count", read_count);
auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
buffer->id = id_;
buffer->cmd = cmd;
buffer->status = PIPE_ERROR_INVAL;
buffer->rw_params.ptrs[0] = paddr;
buffer->rw_params.sizes[0] = static_cast<uint32_t>(count);
buffer->rw_params.ptrs[1] = read_paddr;
buffer->rw_params.sizes[1] = static_cast<uint32_t>(read_count);
buffer->rw_params.buffers_count = read_paddr ? 2 : 1;
buffer->rw_params.consumed_size = 0;
buffer->rw_params.read_index = 1; // Read buffer is always second.
pipe_.Exec(id_);
// Positive consumed size always indicate a successful transfer.
if (buffer->rw_params.consumed_size) {
*actual = buffer->rw_params.consumed_size;
return ZX_OK;
}
*actual = 0;
// Early out if error is not because of back-pressure.
if (buffer->status != PIPE_ERROR_AGAIN) {
LogError("Pipe::Transfer() transfer failed: %d", buffer->status);
return ZX_ERR_INTERNAL;
}
// PIPE_ERROR_AGAIN means that we need to wait until pipe is
// readable/writable before we can perform another transfer command.
// Clear event_ READABLE/WRITABLE bits and request an interrupt that
// will indicate that the pipe is again readable/writable.
event_.signal(state_clr, 0);
buffer->id = id_;
buffer->cmd = wake_cmd;
buffer->status = PIPE_ERROR_INVAL;
pipe_.Exec(id_);
if (buffer->status) {
LogError("Pipe::Transfer() failed to request interrupt: %d", buffer->status);
return ZX_ERR_INTERNAL;
}
return ZX_ERR_SHOULD_WAIT;
}
zx_status_t Pipe::SetBufferSizeLocked(size_t size) {
zx::vmo vmo;
zx_status_t status = zx::vmo::create_contiguous(bti_, size, 0, &vmo);
if (status != ZX_OK) {
LogError("Pipe::CreateBuffer() zx_vmo_create_contiguous failed %d size: %zu", status, size);
return status;
}
zx_paddr_t phys;
zx::pmt pmt;
// We leave pinned continuously, since buffer is expected to be used frequently.
status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo, 0, size, &phys, 1, &pmt);
if (status != ZX_OK) {
LogError("Pipe::CreateBuffer() zx_bti_pin failed %d size: %zu", status, size);
return status;
}
buffer_.vmo = std::move(vmo);
buffer_.pmt = std::move(pmt);
buffer_.size = size;
buffer_.phys = phys;
return ZX_OK;
}
// static
void Pipe::OnSignal(void* ctx, int32_t flags) {
TRACE_DURATION("gfx", "Pipe::OnSignal", "flags", flags);
zx_signals_t state_set = 0;
if (flags & PIPE_WAKE_FLAG_CLOSED) {
state_set |= fuchsia_hardware_goldfish_pipe_SIGNAL_HANGUP;
}
if (flags & PIPE_WAKE_FLAG_READ) {
state_set |= fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE;
}
if (flags & PIPE_WAKE_FLAG_WRITE) {
state_set |= fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE;
}
auto pipe = static_cast<Pipe*>(ctx);
fbl::AutoLock lock(&pipe->lock_);
// The event_ signal is for client code, while the signal_cvar_ is for this class.
pipe->event_.signal(0, state_set);
pipe->signal_cvar_.Signal();
}
} // namespace goldfish