| // 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 "src/graphics/drivers/misc/goldfish/pipe_connection.h" |
| |
| #include <fidl/fuchsia.hardware.goldfish/cpp/wire.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/fzl/owned-vmo-mapper.h> |
| #include <lib/trace/event.h> |
| #include <lib/zx/bti.h> |
| #include <zircon/status.h> |
| |
| #include <fbl/auto_lock.h> |
| |
| #include "src/devices/lib/goldfish/pipe_headers/include/base.h" |
| #include "src/graphics/drivers/misc/goldfish/pipe_device.h" |
| |
| namespace goldfish { |
| namespace { |
| |
| static const char* kTag = "GoldfishPipe"; |
| |
| constexpr size_t DEFAULT_BUFFER_SIZE = 8192; |
| |
| constexpr zx_signals_t SIGNALS = fuchsia_hardware_goldfish::wire::kSignalReadable | |
| fuchsia_hardware_goldfish::wire::kSignalWritable; |
| |
| } // namespace |
| |
| PipeConnection::PipeConnection(PipeDevice* pipe, async_dispatcher_t* dispatcher, OnBindFn on_bind, |
| OnCloseFn on_close) |
| : on_bind_(std::move(on_bind)), |
| on_close_(std::move(on_close)), |
| dispatcher_(dispatcher), |
| pipe_(pipe) {} |
| |
| PipeConnection::~PipeConnection() { |
| fbl::AutoLock lock(&lock_); |
| if (id_) { |
| if (cmd_buffer_.vmo().is_valid()) { |
| auto buffer = static_cast<PipeCmdBuffer*>(cmd_buffer_.start()); |
| buffer->id = id_; |
| buffer->cmd = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kClose); |
| buffer->status = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeError::kInval); |
| |
| pipe_->Exec(id_); |
| ZX_DEBUG_ASSERT(!buffer->status); |
| } |
| pipe_->Destroy(id_); |
| } |
| |
| if (binding_ref_) { |
| binding_ref_->Unbind(); |
| } |
| } |
| |
| void PipeConnection::Init() { |
| fbl::AutoLock lock(&lock_); |
| |
| zx_status_t status = pipe_->GetBti(&bti_); |
| if (status != ZX_OK) { |
| FailAsync(status); |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() GetBti failed: %s", kTag, zx_status_get_string(status)); |
| return; |
| } |
| |
| status = SetBufferSizeLocked(DEFAULT_BUFFER_SIZE); |
| if (status != ZX_OK) { |
| FailAsync(status); |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() failed to set initial buffer size: %s", kTag, |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| zx::event event; |
| status = zx::event::create(0, &event); |
| if (status != ZX_OK) { |
| FailAsync(status); |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() failed to create event: %s", kTag, |
| zx_status_get_string(status)); |
| return; |
| } |
| status = event.signal(0, SIGNALS); |
| ZX_ASSERT(status == ZX_OK); |
| |
| zx::vmo vmo; |
| status = pipe_->Create(&id_, &vmo); |
| if (status != ZX_OK) { |
| FailAsync(status); |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() failed to create pipe: %s", kTag, |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| status = pipe_->SetEvent(id_, std::move(event)); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() failed to set event: %s", kTag, zx_status_get_string(status)); |
| return; |
| } |
| |
| status = |
| cmd_buffer_.Map(std::move(vmo), /*offset=*/0, /*size=*/0, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE); |
| if (status != ZX_OK) { |
| FailAsync(status); |
| FDF_LOG(ERROR, "Failed to map the command buffer VMO: %s", zx_status_get_string(status)); |
| return; |
| } |
| |
| auto buffer = static_cast<PipeCmdBuffer*>(cmd_buffer_.start()); |
| buffer->id = id_; |
| buffer->cmd = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kOpen); |
| buffer->status = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeError::kInval); |
| |
| pipe_->Open(id_); |
| if (buffer->status) { |
| FailAsync(ZX_ERR_INTERNAL); |
| FDF_LOG(ERROR, "[%s] Pipe::Pipe() failed to open pipe", kTag); |
| return; |
| } |
| } |
| |
| void PipeConnection::Bind(fidl::ServerEnd<fuchsia_hardware_goldfish::Pipe> server_request) { |
| using PipeProtocol = fuchsia_hardware_goldfish::Pipe; |
| using PipeServer = fidl::WireServer<PipeProtocol>; |
| auto on_unbound = [this](PipeServer*, fidl::UnbindInfo info, fidl::ServerEnd<PipeProtocol>) { |
| if (info.is_peer_closed() || info.is_user_initiated()) { |
| FDF_LOG(DEBUG, "Pipe closed: %s", info.FormatDescription().c_str()); |
| } else { |
| FDF_LOG(ERROR, "Pipe error: %s", info.FormatDescription().c_str()); |
| } |
| if (on_close_) { |
| on_close_(this); |
| } |
| }; |
| |
| auto binding = |
| fidl::BindServer(dispatcher_, std::move(server_request), this, std::move(on_unbound)); |
| binding_ref_ = |
| std::make_unique<fidl::ServerBindingRef<fuchsia_hardware_goldfish::Pipe>>(std::move(binding)); |
| } |
| |
| void PipeConnection::SetBufferSize(SetBufferSizeRequestView request, |
| SetBufferSizeCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::SetBufferSize", "size", request->size); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| zx_status_t status = SetBufferSizeLocked(request->size); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "[%s] Pipe::SetBufferSize() failed to create buffer: %lu", kTag, request->size); |
| } |
| |
| if (status != ZX_OK && status != ZX_ERR_NO_MEMORY) { |
| completer.Close(status); |
| } else { |
| completer.Reply(status); |
| } |
| } |
| |
| void PipeConnection::SetEvent(SetEventRequestView request, SetEventCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::SetEvent"); |
| |
| if (!request->event.is_valid()) { |
| FDF_LOG(ERROR, "[%s] Pipe::SetEvent() invalid event", kTag); |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| zx_status_t status = pipe_->SetEvent(id_, std::move(request->event)); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "[%s] SetEvent failed: %s", kTag, zx_status_get_string(status)); |
| completer.Close(ZX_ERR_INTERNAL); |
| return; |
| } |
| } |
| |
| void PipeConnection::GetBuffer(GetBufferCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::GetBuffer"); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| zx::vmo vmo; |
| zx_status_t status = buffer_.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "[%s] Pipe::GetBuffer() zx_vmo_duplicate failed: %s", kTag, |
| zx_status_get_string(status)); |
| completer.Close(status); |
| } else { |
| completer.Reply(ZX_OK, std::move(vmo)); |
| } |
| } |
| |
| void PipeConnection::Read(ReadRequestView request, ReadCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::Read", "count", request->count); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if ((request->offset + request->count) > buffer_.size || |
| (request->offset + request->count) < request->offset) { |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| size_t actual; |
| zx_status_t status = |
| TransferLocked(static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kRead), |
| static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnRead), |
| fuchsia_hardware_goldfish::wire::kSignalReadable, |
| buffer_.phys + request->offset, request->count, 0, 0, &actual); |
| completer.Reply(status, actual); |
| } |
| |
| void PipeConnection::Write(WriteRequestView request, WriteCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::Write", "count", request->count); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if ((request->offset + request->count) > buffer_.size || |
| (request->offset + request->count) < request->offset) { |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| size_t actual; |
| zx_status_t status = TransferLocked( |
| static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWrite), |
| static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnWrite), |
| fuchsia_hardware_goldfish::wire::kSignalWritable, buffer_.phys + request->offset, |
| request->count, 0, 0, &actual); |
| completer.Reply(status, actual); |
| } |
| |
| void PipeConnection::DoCall(DoCallRequestView request, DoCallCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Pipe::DoCall", "count", request->count, "read_count", request->read_count); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if ((request->offset + request->count) > buffer_.size || |
| (request->offset + request->count) < request->offset) { |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| if ((request->read_offset + request->read_count) > buffer_.size || |
| (request->read_offset + request->read_count) < request->read_offset) { |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| int32_t cmd = 0, wake_cmd = 0; |
| uint32_t wake_signal = 0u; |
| zx_paddr_t read_paddr = 0u, write_paddr = 0u; |
| // Set write command, signal and offset. |
| if (request->count) { |
| cmd = request->read_count |
| ? static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kCall) |
| : static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWrite); |
| wake_cmd = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnWrite); |
| wake_signal = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnWrite); |
| write_paddr = buffer_.phys + request->offset; |
| } |
| // Set read command, signal and offset. |
| if (request->read_count) { |
| cmd = request->count ? static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kCall) |
| : static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kRead); |
| wake_cmd = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnRead); |
| wake_signal = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeCmdCode::kWakeOnRead); |
| read_paddr = buffer_.phys + request->read_offset; |
| } |
| |
| size_t actual = 0u; |
| zx_status_t status = TransferLocked(cmd, wake_cmd, wake_signal, write_paddr, request->count, |
| read_paddr, request->read_count, &actual); |
| |
| completer.Reply(status, actual); |
| } |
| |
| // 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 PipeConnection::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<PipeCmdBuffer*>(cmd_buffer_.start()); |
| buffer->id = id_; |
| buffer->cmd = cmd; |
| buffer->status = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeError::kInval); |
| 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 != static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeError::kAgain)) { |
| FDF_LOG(ERROR, "[%s] Pipe::Transfer() transfer failed: %d", kTag, buffer->status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| buffer->id = id_; |
| buffer->cmd = wake_cmd; |
| buffer->status = static_cast<int32_t>(fuchsia_hardware_goldfish_pipe::PipeError::kInval); |
| pipe_->Exec(id_); |
| if (buffer->status) { |
| FDF_LOG(ERROR, "[%s] Pipe::Transfer() failed to request interrupt: %d", kTag, buffer->status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| zx_status_t PipeConnection::SetBufferSizeLocked(size_t size) { |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create_contiguous(bti_, size, 0, &vmo); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "[%s] Pipe::CreateBuffer() zx_vmo_create_contiguous failed %d size: %zu", kTag, |
| 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) { |
| FDF_LOG(ERROR, "[%s] Pipe::CreateBuffer() zx_bti_pin failed %d size: %zu", kTag, status, size); |
| return status; |
| } |
| |
| buffer_ = Buffer{.vmo = std::move(vmo), .pmt = std::move(pmt), .size = size, .phys = phys}; |
| return ZX_OK; |
| } |
| |
| void PipeConnection::FailAsync(zx_status_t epitaph) { |
| if (binding_ref_) { |
| binding_ref_->Close(epitaph); |
| } |
| } |
| |
| PipeConnection::Buffer& PipeConnection::Buffer::operator=(PipeConnection::Buffer&& other) noexcept { |
| if (pmt.is_valid()) { |
| pmt.unpin(); |
| } |
| vmo = std::move(other.vmo); |
| pmt = std::move(other.pmt); |
| phys = other.phys; |
| size = other.size; |
| return *this; |
| } |
| |
| PipeConnection::Buffer::~Buffer() { |
| if (pmt.is_valid()) { |
| pmt.unpin(); |
| } |
| } |
| |
| } // namespace goldfish |