blob: 62f08a03e1ec8a4dd30a8224f75daca52ab2464c [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 "display.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/trace/event.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/sysmem/c/fidl.h>
#include <zircon/pixelformat.h>
#include <zircon/threads.h>
#include <memory>
namespace goldfish {
namespace {
const char* kTag = "goldfish-display";
const char* kPipeName = "pipe:opengles";
constexpr uint32_t kRefreshRateHz = 60;
constexpr uint64_t kDisplayId = 1;
constexpr uint32_t kClientFlags = 0;
constexpr zx_pixel_format_t kPixelFormats[] = {
ZX_PIXEL_FORMAT_RGB_x888,
ZX_PIXEL_FORMAT_ARGB_8888,
};
constexpr uint32_t FB_WIDTH = 1;
constexpr uint32_t FB_HEIGHT = 2;
constexpr uint32_t GL_RGBA = 0x1908;
constexpr uint32_t GL_UNSIGNED_BYTE = 0x1401;
constexpr uint32_t IMAGE_TYPE_OPTIMAL = 1;
struct GetFbParamCmd {
uint32_t op;
uint32_t size;
uint32_t param;
};
constexpr uint32_t kOP_rcGetFbParam = 10007;
constexpr uint32_t kSize_rcGetFbParam = 12;
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 OpenColorBufferCmd {
uint32_t op;
uint32_t size;
uint32_t id;
};
constexpr uint32_t kOP_rcOpenColorBuffer = 10013;
constexpr uint32_t kSize_rcOpenColorBuffer = 12;
struct CloseColorBufferCmd {
uint32_t op;
uint32_t size;
uint32_t id;
};
constexpr uint32_t kOP_rcCloseColorBuffer = 10014;
constexpr uint32_t kSize_rcCloseColorBuffer = 12;
struct UpdateColorBufferCmd {
uint32_t op;
uint32_t size;
uint32_t id;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t type;
};
constexpr uint32_t kOP_rcUpdateColorBuffer = 10024;
constexpr uint32_t kSize_rcUpdateColorBuffer = 36;
struct FbPostCmd {
uint32_t op;
uint32_t size;
uint32_t id;
};
constexpr uint32_t kOP_rcFbPost = 10018;
constexpr uint32_t kSize_rcFbPost = 12;
} // namespace
// static
zx_status_t Display::Create(void* ctx, zx_device_t* device) {
auto display = std::make_unique<Display>(device);
zx_status_t status = display->Bind();
if (status == ZX_OK) {
// devmgr now owns device.
__UNUSED auto* dev = display.release();
}
return status;
}
Display::Display(zx_device_t* parent)
: DisplayType(parent), control_(parent), pipe_(parent) {}
Display::~Display() {
if (id_) {
fbl::AutoLock lock(&lock_);
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_);
}
{
fbl::AutoLock lock(&flush_lock_);
shutdown_ = true;
}
thrd_join(flush_thread_, NULL);
}
zx_status_t Display::Bind() {
fbl::AutoLock lock(&lock_);
if (!control_.is_valid()) {
zxlogf(ERROR, "%s: no control protocol\n", kTag);
return ZX_ERR_NOT_SUPPORTED;
}
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 = {Display::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;
}
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));
int rc = thrd_create_with_name(
&flush_thread_,
[](void* arg) { return static_cast<Display*>(arg)->FlushHandler(); },
this, "goldfish_display_flush_thread");
if (rc != thrd_success) {
return thrd_status_to_zx_status(rc);
}
return DdkAdd("goldfish-display");
}
void Display::DdkUnbind() {
DdkRemove();
}
void Display::DdkRelease() {
delete this;
}
void Display::DisplayControllerImplSetDisplayControllerInterface(
const display_controller_interface_protocol_t* interface) {
fbl::AutoLock lock(&flush_lock_);
dc_intf_ = ddk::DisplayControllerInterfaceProtocolClient(interface);
{
fbl::AutoLock lock(&lock_);
width_ = GetFbParamLocked(FB_WIDTH, 1);
height_ = GetFbParamLocked(FB_HEIGHT, 1);
}
added_display_args_t args = {};
args.display_id = kDisplayId;
args.edid_present = false;
args.panel.params.height = height_;
args.panel.params.width = width_;
args.panel.params.refresh_rate_e2 = kRefreshRateHz * 100;
args.pixel_format_list = kPixelFormats;
args.pixel_format_count = sizeof(kPixelFormats) / sizeof(kPixelFormats[0]);
dc_intf_.OnDisplaysChanged(&args, 1, nullptr, 0, nullptr, 0, nullptr);
}
zx_status_t Display::DisplayControllerImplImportVmoImage(image_t* image,
zx::vmo vmo,
size_t offset) {
if (image->type != IMAGE_TYPE_SIMPLE) {
zxlogf(ERROR, "%s: invalid image type\n", kTag);
return ZX_ERR_INVALID_ARGS;
}
auto color_buffer = std::make_unique<ColorBuffer>();
// Linear images must be pinned.
unsigned pixel_size = ZX_PIXEL_FORMAT_BYTES(image->pixel_format);
color_buffer->size =
ROUNDUP(image->width * image->height * pixel_size, PAGE_SIZE);
zx_status_t status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo,
offset, color_buffer->size,
&color_buffer->paddr, 1, &color_buffer->pmt);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to pin VMO: %d\n", kTag, status);
return status;
}
color_buffer->vmo = std::move(vmo);
color_buffer->width = image->width;
color_buffer->height = image->height;
{
fbl::AutoLock lock(&lock_);
status = CreateColorBufferLocked(image->width, image->height,
&color_buffer->id);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to create color buffer\n", kTag);
return status;
}
}
image->handle = reinterpret_cast<uint64_t>(color_buffer.release());
return ZX_OK;
}
zx_status_t Display::DisplayControllerImplImportImage(
image_t* image, zx_unowned_handle_t handle, uint32_t index) {
if (image->type != IMAGE_TYPE_OPTIMAL) {
zxlogf(ERROR, "%s: invalid image type\n", kTag);
return ZX_ERR_INVALID_ARGS;
}
auto color_buffer = std::make_unique<ColorBuffer>();
zx_status_t status, status2;
fuchsia_sysmem_BufferCollectionInfo_2 collection_info;
status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
handle, &status2, &collection_info);
if (status != ZX_OK) {
return status;
}
if (status2 != ZX_OK) {
return status2;
}
if (index < collection_info.buffer_count) {
color_buffer->vmo = zx::vmo(collection_info.buffers[index].vmo);
collection_info.buffers[index].vmo = ZX_HANDLE_INVALID;
}
for (uint32_t i = 0; i < collection_info.buffer_count; ++i) {
zx_handle_close(collection_info.buffers[i].vmo);
}
if (!collection_info.settings.has_image_format_constraints ||
!color_buffer->vmo.is_valid()) {
zxlogf(ERROR, "%s: invalid image format or index\n", kTag);
return ZX_ERR_OUT_OF_RANGE;
}
uint64_t offset = collection_info.buffers[index].vmo_usable_start;
if (offset) {
zxlogf(ERROR, "%s: invalid offset\n", kTag);
return ZX_ERR_INVALID_ARGS;
}
image->handle = reinterpret_cast<uint64_t>(color_buffer.release());
return ZX_OK;
}
void Display::DisplayControllerImplReleaseImage(image_t* image) {
auto color_buffer = reinterpret_cast<ColorBuffer*>(image->handle);
// Color buffer is owned by image in the linear case.
if (image->type == IMAGE_TYPE_SIMPLE) {
fbl::AutoLock lock(&lock_);
CloseColorBufferLocked(color_buffer->id);
}
delete color_buffer;
}
uint32_t Display::DisplayControllerImplCheckConfiguration(
const display_config_t** display_configs, size_t display_count,
uint32_t** layer_cfg_results, size_t* layer_cfg_result_count) {
if (display_count != 1) {
ZX_DEBUG_ASSERT(display_count == 0);
return CONFIG_DISPLAY_OK;
}
ZX_DEBUG_ASSERT(display_configs[0]->display_id == kDisplayId);
bool success;
if (display_configs[0]->layer_count != 1) {
success = false;
} else {
fbl::AutoLock lock(&flush_lock_);
primary_layer_t* layer =
&display_configs[0]->layer_list[0]->cfg.primary;
frame_t frame = {
.x_pos = 0,
.y_pos = 0,
.width = width_,
.height = height_,
};
success =
display_configs[0]->layer_list[0]->type == LAYER_TYPE_PRIMARY &&
layer->transform_mode == FRAME_TRANSFORM_IDENTITY &&
layer->image.width == width_ && layer->image.height == height_ &&
memcmp(&layer->dest_frame, &frame, sizeof(frame_t)) == 0 &&
memcmp(&layer->src_frame, &frame, sizeof(frame_t)) == 0 &&
display_configs[0]->cc_flags == 0 &&
layer->alpha_mode == ALPHA_DISABLE;
}
if (!success) {
layer_cfg_results[0][0] = CLIENT_MERGE_BASE;
for (unsigned i = 1; i < display_configs[0]->layer_count; i++) {
layer_cfg_results[0][i] = CLIENT_MERGE_SRC;
}
layer_cfg_result_count[0] = display_configs[0]->layer_count;
}
return CONFIG_DISPLAY_OK;
}
void Display::DisplayControllerImplApplyConfiguration(
const display_config_t** display_configs, size_t display_count) {
uint64_t handle = 0;
if (display_count && display_configs[0]->layer_count) {
handle = display_configs[0]->layer_list[0]->cfg.primary.image.handle;
}
auto color_buffer = reinterpret_cast<ColorBuffer*>(handle);
if (color_buffer && !color_buffer->id) {
zx::vmo vmo;
zx_status_t status =
color_buffer->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to duplicate vmo: %d\n", kTag, status);
} else {
fbl::AutoLock lock(&lock_);
status = control_.GetColorBuffer(std::move(vmo), &color_buffer->id);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to get color buffer: %d\n", kTag,
status);
}
}
}
{
fbl::AutoLock lock(&flush_lock_);
current_fb_ = color_buffer;
}
}
uint32_t
Display::DisplayControllerImplComputeLinearStride(uint32_t width,
zx_pixel_format_t format) {
return width;
}
zx_status_t Display::DisplayControllerImplAllocateVmo(uint64_t size,
zx::vmo* vmo_out) {
return zx_vmo_create_contiguous(bti_.get(), size, 0,
vmo_out->reset_and_get_address());
}
zx_status_t
Display::DisplayControllerImplGetSysmemConnection(zx::channel connection) {
fbl::AutoLock lock(&lock_);
zx_status_t status = pipe_.ConnectSysmem(std::move(connection));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to connect to sysmem: %d\n", kTag, status);
return status;
}
return ZX_OK;
}
zx_status_t Display::DisplayControllerImplSetBufferCollectionConstraints(
const image_t* config, uint32_t collection) {
fuchsia_sysmem_BufferCollectionConstraints constraints = {};
constraints.usage.display = fuchsia_sysmem_displayUsageLayer;
constraints.has_buffer_memory_constraints = true;
fuchsia_sysmem_BufferMemoryConstraints& buffer_constraints =
constraints.buffer_memory_constraints;
buffer_constraints.min_size_bytes = 0;
buffer_constraints.max_size_bytes = 0xffffffff;
buffer_constraints.physically_contiguous_required = true;
buffer_constraints.secure_required = false;
buffer_constraints.ram_domain_supported = true;
buffer_constraints.cpu_domain_supported = true;
buffer_constraints.inaccessible_domain_supported = true;
buffer_constraints.heap_permitted_count = 2;
buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
buffer_constraints.heap_permitted[1] =
fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL;
constraints.image_format_constraints_count = 1;
fuchsia_sysmem_ImageFormatConstraints& image_constraints =
constraints.image_format_constraints[0];
image_constraints.pixel_format.type = fuchsia_sysmem_PixelFormatType_BGRA32;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = fuchsia_sysmem_ColorSpaceType_SRGB;
image_constraints.min_coded_width = 0;
image_constraints.max_coded_width = 0xffffffff;
image_constraints.min_coded_height = 0;
image_constraints.max_coded_height = 0xffffffff;
image_constraints.min_bytes_per_row = 0;
image_constraints.max_bytes_per_row = 0xffffffff;
image_constraints.max_coded_width_times_coded_height = 0xffffffff;
image_constraints.layers = 1;
image_constraints.coded_width_divisor = 1;
image_constraints.coded_height_divisor = 1;
image_constraints.bytes_per_row_divisor = 1;
image_constraints.start_offset_divisor = 1;
image_constraints.display_width_divisor = 1;
image_constraints.display_height_divisor = 1;
zx_status_t status = fuchsia_sysmem_BufferCollectionSetConstraints(
collection, true, &constraints);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to set constraints\n", kTag);
return status;
}
return ZX_OK;
}
zx_status_t
Display::DisplayControllerImplGetSingleBufferFramebuffer(zx::vmo* out_vmo,
uint32_t* out_stride) {
return ZX_ERR_NOT_SUPPORTED;
}
void Display::OnSignal(void* ctx, int32_t flags) {
TRACE_DURATION("gfx", "Display::OnSignal", "flags", flags);
if (flags & (PIPE_WAKE_FLAG_READ | PIPE_WAKE_FLAG_CLOSED)) {
static_cast<Display*>(ctx)->OnReadable();
}
}
void Display::OnReadable() {
TRACE_DURATION("gfx", "Display::OnReadable");
fbl::AutoLock lock(&lock_);
readable_cvar_.Signal();
}
void Display::WriteLocked(uint32_t cmd_size) {
TRACE_DURATION("gfx", "Display::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 Display::ReadResultLocked(uint32_t* result) {
TRACE_DURATION("gfx", "Display::ReadResult");
while (1) {
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 Display::ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result) {
TRACE_DURATION("gfx", "Display::ExecuteCommand", "cnd_size", cmd_size);
WriteLocked(cmd_size);
return ReadResultLocked(result);
}
int32_t Display::GetFbParamLocked(uint32_t param, int32_t default_value) {
TRACE_DURATION("gfx", "Display::GetFbParam", "param", param);
auto cmd = static_cast<GetFbParamCmd*>(io_buffer_.virt());
cmd->op = kOP_rcGetFbParam;
cmd->size = kSize_rcGetFbParam;
cmd->param = param;
uint32_t result;
zx_status_t status = ExecuteCommandLocked(kSize_rcGetFbParam, &result);
return status == ZX_OK ? result : default_value;
}
zx_status_t Display::CreateColorBufferLocked(uint32_t width, uint32_t height,
uint32_t* id) {
TRACE_DURATION("gfx", "Display::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 = GL_RGBA;
return ExecuteCommandLocked(kSize_rcCreateColorBuffer, id);
}
void Display::OpenColorBufferLocked(uint32_t id) {
TRACE_DURATION("gfx", "Display::OpenColorBuffer", "id", id);
auto cmd = static_cast<OpenColorBufferCmd*>(io_buffer_.virt());
cmd->op = kOP_rcOpenColorBuffer;
cmd->size = kSize_rcOpenColorBuffer;
cmd->id = id;
WriteLocked(kSize_rcOpenColorBuffer);
}
void Display::CloseColorBufferLocked(uint32_t id) {
TRACE_DURATION("gfx", "Display::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 Display::UpdateColorBufferLocked(uint32_t id, zx_paddr_t paddr,
uint32_t width, uint32_t height,
size_t size, uint32_t* result) {
TRACE_DURATION("gfx", "Display::UpdateColorBuffer", "size", size);
auto cmd = static_cast<UpdateColorBufferCmd*>(io_buffer_.virt());
cmd->op = kOP_rcUpdateColorBuffer;
cmd->size = kSize_rcUpdateColorBuffer + static_cast<uint32_t>(size);
cmd->id = id;
cmd->x = 0;
cmd->y = 0;
cmd->width = width;
cmd->height = height;
cmd->format = GL_RGBA;
cmd->type = GL_UNSIGNED_BYTE;
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.ptrs[1] = paddr;
buffer->rw_params.sizes[0] = kSize_rcUpdateColorBuffer;
buffer->rw_params.sizes[1] = static_cast<uint32_t>(size);
buffer->rw_params.buffers_count = 2;
buffer->rw_params.consumed_size = 0;
pipe_.Exec(id_);
ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size ==
static_cast<int32_t>(kSize_rcUpdateColorBuffer + size));
return ReadResultLocked(result);
}
void Display::FbPostLocked(uint32_t id) {
TRACE_DURATION("gfx", "Display::FbPost", "id", id);
auto cmd = static_cast<FbPostCmd*>(io_buffer_.virt());
cmd->op = kOP_rcFbPost;
cmd->size = kSize_rcFbPost;
cmd->id = id;
WriteLocked(kSize_rcFbPost);
}
int Display::FlushHandler() {
zx_time_t next_deadline = zx_clock_get_monotonic();
zx_time_t period = ZX_SEC(1) / kRefreshRateHz;
while (1) {
zx_nanosleep(next_deadline);
ColorBuffer* displayed_fb;
{
fbl::AutoLock lock(&flush_lock_);
if (shutdown_)
break;
displayed_fb = current_fb_;
}
if (displayed_fb) {
fbl::AutoLock lock(&lock_);
if (displayed_fb->paddr) {
uint32_t result;
zx_status_t status = UpdateColorBufferLocked(
displayed_fb->id, displayed_fb->paddr,
displayed_fb->width, displayed_fb->height,
displayed_fb->size, &result);
if (status != ZX_OK || result) {
zxlogf(ERROR, "%s: color buffer update failed\n", kTag);
continue;
}
}
FbPostLocked(displayed_fb->id);
}
{
fbl::AutoLock lock(&flush_lock_);
if (dc_intf_.is_valid()) {
uint64_t handles[] = {reinterpret_cast<uint64_t>(displayed_fb)};
dc_intf_.OnDisplayVsync(kDisplayId, next_deadline, handles,
displayed_fb ? 1 : 0);
}
}
next_deadline = zx_time_add_duration(next_deadline, period);
}
return 0;
}
} // namespace goldfish
static zx_driver_ops_t goldfish_display_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops;
ops.version = DRIVER_OPS_VERSION;
ops.bind = goldfish::Display::Create;
return ops;
}();
// clang-format off
ZIRCON_DRIVER_BEGIN(goldfish_display, goldfish_display_driver_ops, "zircon",
"0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_GOLDFISH_CONTROL),
ZIRCON_DRIVER_END(goldfish_display)
// clang-format on