| // 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 "control-device.h" |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/trace/event.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/unique_ptr.h> |
| #include <fuchsia/sysmem/c/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fidl-async-2/fidl_server.h> |
| #include <lib/fidl-async-2/simple_binding.h> |
| #include <lib/fidl-utils/bind.h> |
| #include <lib/zx/event.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| |
| namespace goldfish { |
| namespace { |
| |
| const char* kTag = "goldfish-control"; |
| |
| const char* kPipeName = "pipe:opengles"; |
| |
| constexpr uint32_t kClientFlags = 0; |
| |
| constexpr uint32_t VULKAN_ONLY = 1; |
| |
| struct CreateColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t width; |
| uint32_t height; |
| uint32_t internalformat; |
| }; |
| constexpr uint32_t kOP_rcCreateColorBuffer = 10012; |
| constexpr uint32_t kSize_rcCreateColorBuffer = 20; |
| |
| struct CloseColorBufferCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| }; |
| constexpr uint32_t kOP_rcCloseColorBuffer = 10014; |
| constexpr uint32_t kSize_rcCloseColorBuffer = 12; |
| |
| struct SetColorBufferVulkanModeCmd { |
| uint32_t op; |
| uint32_t size; |
| uint32_t id; |
| uint32_t mode; |
| }; |
| constexpr uint32_t kOP_rcSetColorBufferVulkanMode = 10045; |
| constexpr uint32_t kSize_rcSetColorBufferVulkanMode = 16; |
| |
| zx_koid_t GetKoidForVmo(const zx::vmo& vmo) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = zx_object_get_info( |
| vmo.get(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: zx_object_get_info() failed - status: %d\n", kTag, |
| status); |
| return ZX_KOID_INVALID; |
| } |
| return info.koid; |
| } |
| |
| 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()); |
| } |
| } |
| |
| constexpr uint32_t kConcurrencyCap = 64; |
| |
| // An instance of this class serves a Heap connection. |
| class Heap : public FidlServer<Heap, |
| SimpleBinding<Heap, fuchsia_sysmem_Heap_ops_t, |
| fuchsia_sysmem_Heap_dispatch>, |
| vLog> { |
| public: |
| // Public for std::unique_ptr<Heap>: |
| ~Heap() = default; |
| |
| private: |
| friend class FidlServer; |
| |
| Heap(Control* control) |
| : FidlServer("GoldfishHeap", kConcurrencyCap), control_(control) {} |
| |
| zx_status_t AllocateVmo(uint64_t size, fidl_txn* txn) { |
| BindingType::Txn::RecognizeTxn(txn); |
| |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(size, 0, &vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, |
| "%s: zx::vmo::create() failed - size: %lu status: %d\n", |
| kTag, size, status); |
| return fuchsia_sysmem_HeapAllocateVmo_reply(txn, status, |
| ZX_HANDLE_INVALID); |
| } |
| |
| return fuchsia_sysmem_HeapAllocateVmo_reply(txn, ZX_OK, vmo.release()); |
| } |
| |
| zx_status_t CreateResource(zx_handle_t vmo_handle, fidl_txn* txn) { |
| BindingType::Txn::RecognizeTxn(txn); |
| |
| zx::vmo vmo(vmo_handle); |
| |
| zx_koid_t id = GetKoidForVmo(vmo); |
| if (id == ZX_KOID_INVALID) { |
| return fuchsia_sysmem_HeapCreateResource_reply( |
| txn, ZX_ERR_INVALID_ARGS, 0); |
| } |
| |
| control_->RegisterColorBuffer(id); |
| return fuchsia_sysmem_HeapCreateResource_reply(txn, ZX_OK, id); |
| } |
| |
| zx_status_t DestroyResource(uint64_t id, fidl_txn* txn) { |
| BindingType::Txn::RecognizeTxn(txn); |
| |
| control_->FreeColorBuffer(id); |
| return fuchsia_sysmem_HeapDestroyResource_reply(txn); |
| } |
| |
| static constexpr fuchsia_sysmem_Heap_ops_t kOps = { |
| fidl::Binder<Heap>::BindMember<&Heap::AllocateVmo>, |
| fidl::Binder<Heap>::BindMember<&Heap::CreateResource>, |
| fidl::Binder<Heap>::BindMember<&Heap::DestroyResource>, |
| }; |
| |
| Control* const control_; |
| }; |
| |
| } // namespace |
| |
| // static |
| zx_status_t Control::Create(void* ctx, zx_device_t* device) { |
| auto control = std::make_unique<Control>(device); |
| |
| zx_status_t status = control->Bind(); |
| if (status == ZX_OK) { |
| // devmgr now owns device. |
| __UNUSED auto* dev = control.release(); |
| } |
| return status; |
| } |
| |
| Control::Control(zx_device_t* parent) |
| : ControlType(parent), pipe_(parent), |
| heap_loop_(&kAsyncLoopConfigNoAttachToThread) { |
| goldfish_control_protocol_t self{&goldfish_control_protocol_ops_, this}; |
| control_ = ddk::GoldfishControlProtocolClient(&self); |
| } |
| |
| Control::~Control() { |
| heap_loop_.Shutdown(); |
| if (id_) { |
| fbl::AutoLock lock(&lock_); |
| if (cmd_buffer_.is_valid()) { |
| for (auto& buffer : color_buffers_) { |
| CloseColorBufferLocked(buffer.second); |
| } |
| 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 Control::Bind() { |
| fbl::AutoLock lock(&lock_); |
| |
| 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 = |
| io_buffer_.Init(bti_.get(), PAGE_SIZE, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: io_buffer_init failed: %d\n", kTag, status); |
| return status; |
| } |
| |
| zx::vmo vmo; |
| goldfish_pipe_signal_value_t signal_cb = {Control::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 release_buffer = |
| fbl::MakeAutoCall([this]() TA_NO_THREAD_SAFETY_ANALYSIS |
| { cmd_buffer_.release(); }); |
| |
| 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); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Keep buffer after successful execution of OPEN command. This way |
| // we'll send CLOSE later. |
| release_buffer.cancel(); |
| |
| size_t length = strlen(kPipeName) + 1; |
| memcpy(io_buffer_.virt(), kPipeName, length); |
| WriteLocked(static_cast<uint32_t>(length)); |
| |
| memcpy(io_buffer_.virt(), &kClientFlags, sizeof(kClientFlags)); |
| WriteLocked(sizeof(kClientFlags)); |
| |
| // We are now ready to serve goldfish heap allocations. Create a channel |
| // and register client-end with sysmem. |
| zx::channel heap_request, heap_connection; |
| status = zx::channel::create(0, &heap_request, &heap_connection); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: zx::channel:create() failed: %d\n", kTag, status); |
| return status; |
| } |
| status = |
| pipe_.RegisterSysmemHeap(fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL, |
| std::move(heap_connection)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to register heap: %d\n", kTag, status); |
| return status; |
| } |
| |
| // Start server thread. Heap server must be running on a seperate |
| // thread as sysmem might be making synchronous allocation requests |
| // from the main thread. |
| heap_loop_.StartThread("goldfish_control_heap_thread"); |
| async::PostTask(heap_loop_.dispatcher(), |
| [this, request = std::move(heap_request)]() mutable { |
| // The Heap is channel-owned / self-owned. |
| Heap::CreateChannelOwned(std::move(request), this); |
| }); |
| |
| return DdkAdd("goldfish-control", 0, nullptr, 0, |
| ZX_PROTOCOL_GOLDFISH_CONTROL); |
| } |
| |
| void Control::RegisterColorBuffer(zx_koid_t koid) { |
| fbl::AutoLock lock(&lock_); |
| color_buffers_[koid] = 0; |
| } |
| |
| void Control::FreeColorBuffer(zx_koid_t koid) { |
| fbl::AutoLock lock(&lock_); |
| |
| auto it = color_buffers_.find(koid); |
| if (it == color_buffers_.end()) { |
| zxlogf(ERROR, "%s: invalid key\n", kTag); |
| return; |
| } |
| |
| if (it->second) { |
| CloseColorBufferLocked(it->second); |
| } |
| color_buffers_.erase(it); |
| } |
| |
| zx_status_t Control::FidlCreateColorBuffer(zx_handle_t vmo_handle, |
| uint32_t width, uint32_t height, |
| uint32_t format, fidl_txn_t* txn) { |
| TRACE_DURATION("gfx", "Control::FidlCreateColorBuffer", "width", width, |
| "height", height); |
| |
| zx::vmo vmo(vmo_handle); |
| zx_koid_t koid = GetKoidForVmo(vmo); |
| if (koid == ZX_KOID_INVALID) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| auto it = color_buffers_.find(koid); |
| if (it == color_buffers_.end()) { |
| zxlogf(ERROR, "%s: invalid VMO\n", kTag); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (it->second) { |
| zxlogf(ERROR, "%s: color buffer already exists\n", kTag); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t id; |
| zx_status_t status = CreateColorBufferLocked(width, height, format, &id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: failed to create color buffer: %d\n", kTag, status); |
| return status; |
| } |
| |
| auto close_color_buffer = |
| fbl::MakeAutoCall([this, id]() TA_NO_THREAD_SAFETY_ANALYSIS { |
| CloseColorBufferLocked(id); |
| }); |
| |
| uint32_t result = 0; |
| status = SetColorBufferVulkanModeLocked(id, VULKAN_ONLY, &result); |
| if (status != ZX_OK || result) { |
| zxlogf(ERROR, "%s: failed to set vulkan mode: %d %d\n", kTag, status, |
| result); |
| return status; |
| } |
| |
| close_color_buffer.cancel(); |
| it->second = id; |
| return fuchsia_hardware_goldfish_control_DeviceCreateColorBuffer_reply( |
| txn, ZX_OK); |
| } |
| |
| zx_status_t Control::FidlGetColorBuffer(zx_handle_t vmo_handle, |
| fidl_txn_t* txn) { |
| TRACE_DURATION("gfx", "Control::FidlGetColorBuffer"); |
| |
| zx::vmo vmo(vmo_handle); |
| zx_koid_t koid = GetKoidForVmo(vmo); |
| if (koid == ZX_KOID_INVALID) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| auto it = color_buffers_.find(koid); |
| if (it == color_buffers_.end()) { |
| return fuchsia_hardware_goldfish_control_DeviceGetColorBuffer_reply( |
| txn, ZX_ERR_INVALID_ARGS, 0); |
| } |
| |
| return fuchsia_hardware_goldfish_control_DeviceGetColorBuffer_reply( |
| txn, ZX_OK, it->second); |
| } |
| |
| void Control::DdkUnbind() { |
| DdkRemove(); |
| } |
| |
| void Control::DdkRelease() { |
| delete this; |
| } |
| |
| zx_status_t Control::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| using Binder = fidl::Binder<Control>; |
| |
| static const fuchsia_hardware_goldfish_control_Device_ops_t kOps = { |
| .CreateColorBuffer = |
| Binder::BindMember<&Control::FidlCreateColorBuffer>, |
| .GetColorBuffer = Binder::BindMember<&Control::FidlGetColorBuffer>, |
| }; |
| |
| return fuchsia_hardware_goldfish_control_Device_dispatch(this, txn, msg, |
| &kOps); |
| } |
| |
| zx_status_t Control::DdkGetProtocol(uint32_t proto_id, void* out_protocol) { |
| fbl::AutoLock lock(&lock_); |
| |
| switch (proto_id) { |
| case ZX_PROTOCOL_GOLDFISH_PIPE: { |
| pipe_.GetProto(static_cast<goldfish_pipe_protocol_t*>(out_protocol)); |
| return ZX_OK; |
| } |
| case ZX_PROTOCOL_GOLDFISH_CONTROL: { |
| control_.GetProto( |
| static_cast<goldfish_control_protocol_t*>(out_protocol)); |
| return ZX_OK; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t Control::GoldfishControlGetColorBuffer(zx::vmo vmo, |
| uint32_t* out_id) { |
| zx_koid_t koid = GetKoidForVmo(vmo); |
| if (koid == ZX_KOID_INVALID) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| auto it = color_buffers_.find(koid); |
| if (it == color_buffers_.end()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| *out_id = it->second; |
| return ZX_OK; |
| } |
| |
| void Control::OnSignal(void* ctx, int32_t flags) { |
| TRACE_DURATION("gfx", "Control::OnSignal", "flags", flags); |
| |
| if (flags & (PIPE_WAKE_FLAG_READ | PIPE_WAKE_FLAG_CLOSED)) { |
| static_cast<Control*>(ctx)->OnReadable(); |
| } |
| } |
| |
| void Control::OnReadable() { |
| TRACE_DURATION("gfx", "Control::OnReadable"); |
| |
| fbl::AutoLock lock(&lock_); |
| readable_cvar_.Signal(); |
| } |
| |
| void Control::WriteLocked(uint32_t cmd_size) { |
| TRACE_DURATION("gfx", "Control::Write", "cmd_size", cmd_size); |
| |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WRITE; |
| buffer->status = PIPE_ERROR_INVAL; |
| buffer->rw_params.ptrs[0] = io_buffer_.phys(); |
| buffer->rw_params.sizes[0] = cmd_size; |
| buffer->rw_params.buffers_count = 1; |
| buffer->rw_params.consumed_size = 0; |
| pipe_.Exec(id_); |
| ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size == |
| static_cast<int32_t>(cmd_size)); |
| } |
| |
| zx_status_t Control::ReadResultLocked(uint32_t* result) { |
| TRACE_DURATION("gfx", "Control::ReadResult"); |
| |
| while (true) { |
| auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt()); |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_READ; |
| buffer->status = PIPE_ERROR_INVAL; |
| buffer->rw_params.ptrs[0] = io_buffer_.phys(); |
| buffer->rw_params.sizes[0] = sizeof(*result); |
| 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) { |
| ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size == sizeof(*result)); |
| *result = *static_cast<uint32_t*>(io_buffer_.virt()); |
| return ZX_OK; |
| } |
| |
| // Early out if error is not because of back-pressure. |
| if (buffer->status != PIPE_ERROR_AGAIN) { |
| zxlogf(ERROR, "%s: reading result failed: %d\n", kTag, |
| buffer->status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| buffer->id = id_; |
| buffer->cmd = PIPE_CMD_CODE_WAKE_ON_READ; |
| buffer->status = PIPE_ERROR_INVAL; |
| pipe_.Exec(id_); |
| ZX_DEBUG_ASSERT(!buffer->status); |
| |
| // Wait for pipe to become readable. |
| readable_cvar_.Wait(&lock_); |
| } |
| } |
| |
| zx_status_t Control::ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result) { |
| TRACE_DURATION("gfx", "Control::ExecuteCommand", "cnd_size", cmd_size); |
| |
| WriteLocked(cmd_size); |
| return ReadResultLocked(result); |
| } |
| |
| zx_status_t Control::CreateColorBufferLocked(uint32_t width, uint32_t height, |
| uint32_t format, uint32_t* id) { |
| TRACE_DURATION("gfx", "Control::CreateColorBuffer", "width", width, |
| "height", height); |
| |
| auto cmd = static_cast<CreateColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcCreateColorBuffer; |
| cmd->size = kSize_rcCreateColorBuffer; |
| cmd->width = width; |
| cmd->height = height; |
| cmd->internalformat = format; |
| |
| return ExecuteCommandLocked(kSize_rcCreateColorBuffer, id); |
| } |
| |
| void Control::CloseColorBufferLocked(uint32_t id) { |
| TRACE_DURATION("gfx", "Control::CloseColorBuffer", "id", id); |
| |
| auto cmd = static_cast<CloseColorBufferCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcCloseColorBuffer; |
| cmd->size = kSize_rcCloseColorBuffer; |
| cmd->id = id; |
| |
| WriteLocked(kSize_rcCloseColorBuffer); |
| } |
| |
| zx_status_t Control::SetColorBufferVulkanModeLocked(uint32_t id, uint32_t mode, |
| uint32_t* result) { |
| TRACE_DURATION("gfx", "Control::SetColorBufferVulkanMode", "id", id, "mode", |
| mode); |
| |
| auto cmd = static_cast<SetColorBufferVulkanModeCmd*>(io_buffer_.virt()); |
| cmd->op = kOP_rcSetColorBufferVulkanMode; |
| cmd->size = kSize_rcSetColorBufferVulkanMode; |
| cmd->id = id; |
| cmd->mode = mode; |
| |
| return ExecuteCommandLocked(kSize_rcSetColorBufferVulkanMode, result); |
| } |
| |
| } // namespace goldfish |
| |
| static zx_driver_ops_t goldfish_control_driver_ops = []() -> zx_driver_ops_t { |
| zx_driver_ops_t ops; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = goldfish::Control::Create; |
| return ops; |
| }(); |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(goldfish_control, goldfish_control_driver_ops, "zircon", |
| "0.1", 1) |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_GOLDFISH_PIPE), |
| ZIRCON_DRIVER_END(goldfish_control) |
| // clang-format on |