blob: 3bd75bd1c5632bd20f7193d49bb74e2a035bac1b [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 "instance.h"
#include <ddk/debug.h>
#include <ddk/trace/event.h>
#include <fuchsia/hardware/goldfish/pipe/c/fidl.h>
#include <lib/fidl-utils/bind.h>
#include <lib/zx/bti.h>
#include <algorithm>
namespace goldfish {
namespace {
const char* kTag = "goldfish-pipe";
constexpr size_t DEFAULT_BUFFER_SIZE = 8192;
constexpr zx_signals_t SIGNALS =
fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE |
fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE;
} // namespace
Instance::Instance(zx_device_t* parent) : InstanceType(parent), pipe_(parent) {}
Instance::~Instance() {
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_);
}
}
zx_status_t Instance::Bind() {
TRACE_DURATION("gfx", "Instance::Bind");
if (!pipe_.is_valid()) {
zxlogf(ERROR, "%s: no pipe protocol\n", kTag);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status = pipe_.GetBti(&bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: GetBti failed: %d\n", kTag, status);
return status;
}
status = SetBufferSize(DEFAULT_BUFFER_SIZE);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to set initial buffer size\n", kTag);
return status;
}
status = zx::event::create(0, &buffer_.event);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to set initial buffer size\n", kTag);
return status;
}
buffer_.event.signal(0, SIGNALS);
zx::vmo vmo;
goldfish_pipe_signal_value_t signal_cb = {Instance::OnSignal, this};
status = pipe_.Create(&signal_cb, &id_, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Create failed: %d\n", kTag, status);
return status;
}
status = cmd_buffer_.InitVmo(bti_.get(), vmo.get(), 0, IO_BUFFER_RW);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: io_buffer_init_vmo failed: %d\n", kTag, status);
return status;
}
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) {
zxlogf(ERROR, "%s: Open failed: %d\n", kTag, buffer->status);
cmd_buffer_.release();
return ZX_ERR_INTERNAL;
}
return DdkAdd("pipe", DEVICE_ADD_INSTANCE);
}
zx_status_t Instance::FidlSetBufferSize(uint64_t size, fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Instance::FidlSetBufferSize", "size", size);
zx_status_t status = SetBufferSize(size);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to set buffer size: %lu\n", kTag, size);
return status;
}
return fuchsia_hardware_goldfish_pipe_DeviceSetBufferSize_reply(txn,
status);
}
zx_status_t Instance::FidlSetEvent(zx_handle_t event_handle) {
TRACE_DURATION("gfx", "Instance::FidlSetEvent");
zx::event event(event_handle);
if (!event.is_valid()) {
zxlogf(ERROR, "%s: invalid event\n", kTag);
return ZX_ERR_INVALID_ARGS;
}
zx_handle_t observed = 0;
zx_status_t status =
zx_object_wait_one(buffer_.event.get(), SIGNALS, 0, &observed);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to transfer observed signals: %d\n", kTag,
status);
return status;
}
buffer_.event = std::move(event);
buffer_.event.signal(SIGNALS, observed);
return ZX_OK;
}
zx_status_t Instance::FidlGetBuffer(fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Instance::FidlGetBuffer");
zx::vmo vmo;
zx_status_t status = buffer_.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_vmo_duplicate failed: %d\n", kTag, status);
return status;
}
return fuchsia_hardware_goldfish_pipe_DeviceGetBuffer_reply(txn, ZX_OK,
vmo.release());
}
zx_status_t Instance::FidlRead(size_t count, zx_off_t offset, fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Instance::FidlRead", "count", count);
if ((offset + count) > buffer_.size)
return ZX_ERR_INVALID_ARGS;
size_t actual;
zx_status_t status = Read(buffer_.phys + offset, count, &actual);
return fuchsia_hardware_goldfish_pipe_DeviceRead_reply(txn, status, actual);
}
zx_status_t Instance::FidlWrite(size_t count, zx_off_t offset,
fidl_txn_t* txn) {
TRACE_DURATION("gfx", "Instance::FidlWrite", "count", count);
if ((offset + count) > buffer_.size)
return ZX_ERR_INVALID_ARGS;
size_t actual;
zx_status_t status = Write(buffer_.phys + offset, count, &actual);
return fuchsia_hardware_goldfish_pipe_DeviceWrite_reply(txn, status,
actual);
}
zx_status_t Instance::DdkRead(void* buf, size_t buf_len, zx_off_t off,
size_t* actual) {
TRACE_DURATION("gfx", "Instance::DdkRead", "buf_len", buf_len);
size_t count = std::min(buf_len, buffer_.size);
zx_status_t status = Read(buffer_.phys, count, actual);
if (status != ZX_OK) {
return status;
}
status = buffer_.vmo.read(buf, 0, *actual);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_vmo_read failed %d size: %zu\n", kTag, status,
*actual);
return status;
}
return ZX_OK;
}
zx_status_t Instance::DdkWrite(const void* buf, size_t buf_len, zx_off_t off,
size_t* actual) {
TRACE_DURATION("gfx", "Instance::DdkWrite", "buf_len", buf_len);
size_t count = std::min(buf_len, buffer_.size);
zx_status_t status = buffer_.vmo.write(buf, 0, count);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_vmo_write failed %d size: %zu\n", kTag, status,
count);
return status;
}
return Write(buffer_.phys, count, actual);
}
zx_status_t Instance::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
using Binder = fidl::Binder<Instance>;
static const fuchsia_hardware_goldfish_pipe_Device_ops_t kOps = {
.SetBufferSize = Binder::BindMember<&Instance::FidlSetBufferSize>,
.SetEvent = Binder::BindMember<&Instance::FidlSetEvent>,
.GetBuffer = Binder::BindMember<&Instance::FidlGetBuffer>,
.Read = Binder::BindMember<&Instance::FidlRead>,
.Write = Binder::BindMember<&Instance::FidlWrite>,
};
return fuchsia_hardware_goldfish_pipe_Device_dispatch(this, txn, msg,
&kOps);
}
zx_status_t Instance::DdkClose(uint32_t flags) {
return ZX_OK;
}
void Instance::DdkRelease() {
delete this;
}
// static
void Instance::OnSignal(void* ctx, int32_t flags) {
TRACE_DURATION("gfx", "Instance::OnSignal", "flags", flags);
zx_signals_t dev_state_set = 0;
zx_signals_t state_set = 0;
if (flags & PIPE_WAKE_FLAG_CLOSED) {
dev_state_set = DEV_STATE_HANGUP;
state_set = fuchsia_hardware_goldfish_pipe_SIGNAL_HANGUP;
}
if (flags & PIPE_WAKE_FLAG_READ) {
dev_state_set = DEV_STATE_READABLE;
state_set = fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE;
}
if (flags & PIPE_WAKE_FLAG_WRITE) {
dev_state_set = DEV_STATE_WRITABLE;
state_set = fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE;
}
auto instance = static_cast<Instance*>(ctx);
instance->SetState(dev_state_set);
instance->buffer_.event.signal(0, state_set);
}
zx_status_t Instance::SetBufferSize(size_t size) {
zx::vmo vmo;
zx_status_t status = zx::vmo::create_contiguous(bti_, size, 0, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_vmo_create_contiguous failed %d size: %zu\n",
kTag, status, size);
return status;
}
zx_paddr_t phys;
zx::pmt pmt;
status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo, 0, size, &phys,
1, &pmt);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_bti_pin failed %d size: %zu\n", kTag, status,
size);
return status;
}
buffer_.vmo = std::move(vmo);
buffer_.pmt = std::move(pmt);
buffer_.size = size;
buffer_.phys = phys;
return ZX_OK;
}
zx_status_t Instance::Read(zx_paddr_t paddr, size_t count, size_t* actual) {
return Transfer(PIPE_CMD_CODE_READ, PIPE_CMD_CODE_WAKE_ON_READ,
fuchsia_hardware_goldfish_pipe_SIGNAL_READABLE,
DEV_STATE_READABLE, paddr, count, actual);
}
zx_status_t Instance::Write(zx_paddr_t paddr, size_t count, size_t* actual) {
return Transfer(PIPE_CMD_CODE_WRITE, PIPE_CMD_CODE_WAKE_ON_WRITE,
fuchsia_hardware_goldfish_pipe_SIGNAL_WRITABLE,
DEV_STATE_WRITABLE, paddr, count, actual);
}
zx_status_t Instance::Transfer(int32_t cmd, int32_t wake_cmd,
zx_signals_t state_clr,
zx_signals_t dev_state_clr, zx_paddr_t paddr,
size_t count, size_t* actual) {
TRACE_DURATION("gfx", "Instance::Transfer", "count", 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.buffers_count = 1;
buffer->rw_params.consumed_size = 0;
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) {
zxlogf(ERROR, "%s: transfer failed: %d\n", kTag, 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.
// Remove device state and request an interrupt that will indicate
// that the pipe is again readable/writable.
ClearState(dev_state_clr);
buffer_.event.signal(state_clr, 0);
buffer->id = id_;
buffer->cmd = wake_cmd;
buffer->status = PIPE_ERROR_INVAL;
pipe_.Exec(id_);
if (buffer->status) {
zxlogf(ERROR, "%s: failed to request interrupt: %d\n", kTag,
buffer->status);
return ZX_ERR_INTERNAL;
}
return ZX_ERR_SHOULD_WAIT;
}
} // namespace goldfish