[zircon][dev][goldfish] Add goldfish display driver

DX-939 #comment

This adds a goldfish display controller implementation. The
change is a critical step towards AEMU support and testing
Fuchsia UI without access to Fuchsia supported hardware.

The current framebuffer is transferred to host at each refresh
interval so performance is not expected to be great when using
linear buffers. This can be improved by using address space
device and mapping host side memory into physical address space.

Non-linear (Vulkan) buffer handling is more efficient as
uploads to color buffers are not needed.

This change is part of a series of changes that allow
Fuchsia to run in AEMU, which makes it possible to run a
large set of our existing UI unit/integration tests
without Fuchsia hardware.

Test: gfxtest
Test: display-test --simple
Change-Id: Ib68dc1c23e25fcf921102aa6c7765435f18648ba
diff --git a/zircon/system/dev/display/BUILD.gn b/zircon/system/dev/display/BUILD.gn
index 67d63b3..cc7e8bb 100644
--- a/zircon/system/dev/display/BUILD.gn
+++ b/zircon/system/dev/display/BUILD.gn
@@ -10,6 +10,7 @@
     "dsi-dw",
     "dsi-mt",
     "dummy",
+    "goldfish-display",
     "hikey-display",
     "led2472g",
     "mt8167s-display",
diff --git a/zircon/system/dev/display/goldfish-display/BUILD.gn b/zircon/system/dev/display/goldfish-display/BUILD.gn
new file mode 100644
index 0000000..1d894f6
--- /dev/null
+++ b/zircon/system/dev/display/goldfish-display/BUILD.gn
@@ -0,0 +1,22 @@
+# 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.
+
+driver("goldfish-display") {
+  sources = [
+    "display.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.display.controller",
+    "$zx/system/banjo/ddk.protocol.goldfish.control",
+    "$zx/system/banjo/ddk.protocol.goldfish.pipe",
+    "$zx/system/banjo/ddk.protocol.sysmem",
+    "$zx/system/fidl/fuchsia-sysmem:c",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/trace:headers",
+    "$zx/system/ulib/trace:trace-driver",
+    "$zx/system/ulib/zx",
+  ]
+}
diff --git a/zircon/system/dev/display/goldfish-display/display.cpp b/zircon/system/dev/display/goldfish-display/display.cpp
new file mode 100644
index 0000000..62f08a0
--- /dev/null
+++ b/zircon/system/dev/display/goldfish-display/display.cpp
@@ -0,0 +1,724 @@
+// 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
diff --git a/zircon/system/dev/display/goldfish-display/display.h b/zircon/system/dev/display/goldfish-display/display.h
new file mode 100644
index 0000000..7588841
--- /dev/null
+++ b/zircon/system/dev/display/goldfish-display/display.h
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef ZIRCON_SYSTEM_DEV_DISPLAY_GOLDFISH_DISPLAY_DISPLAY_H_
+#define ZIRCON_SYSTEM_DEV_DISPLAY_GOLDFISH_DISPLAY_DISPLAY_H_
+
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/display/controller.h>
+#include <ddktl/protocol/goldfish/control.h>
+#include <ddktl/protocol/goldfish/pipe.h>
+#include <fbl/condition_variable.h>
+#include <fbl/mutex.h>
+#include <lib/zx/pmt.h>
+#include <threads.h>
+#include <zircon/thread_annotations.h>
+#include <zircon/types.h>
+
+namespace goldfish {
+
+class Display;
+using DisplayType = ddk::Device<Display, ddk::Unbindable>;
+
+class Display
+    : public DisplayType,
+      public ddk::DisplayControllerImplProtocol<Display, ddk::base_protocol> {
+public:
+    static zx_status_t Create(void* ctx, zx_device_t* parent);
+
+    explicit Display(zx_device_t* parent);
+    ~Display();
+
+    zx_status_t Bind();
+
+    // Device protocol implementation.
+    void DdkUnbind();
+    void DdkRelease();
+
+    // Display controller protocol implementation.
+    void DisplayControllerImplSetDisplayControllerInterface(
+        const display_controller_interface_protocol_t* interface);
+    zx_status_t DisplayControllerImplImportVmoImage(image_t* image, zx::vmo vmo,
+                                                    size_t offset);
+    zx_status_t DisplayControllerImplImportImage(image_t* image,
+                                                 zx_unowned_handle_t handle,
+                                                 uint32_t index);
+    void DisplayControllerImplReleaseImage(image_t* image);
+    uint32_t DisplayControllerImplCheckConfiguration(
+        const display_config_t** display_configs, size_t display_count,
+        uint32_t** layer_cfg_results, size_t* layer_cfg_result_count);
+    void DisplayControllerImplApplyConfiguration(
+        const display_config_t** display_config, size_t display_count);
+    uint32_t DisplayControllerImplComputeLinearStride(uint32_t width,
+                                                      zx_pixel_format_t format);
+    zx_status_t DisplayControllerImplAllocateVmo(uint64_t size,
+                                                 zx::vmo* vmo_out);
+    zx_status_t
+    DisplayControllerImplGetSysmemConnection(zx::channel connection);
+    zx_status_t
+    DisplayControllerImplSetBufferCollectionConstraints(const image_t* config,
+                                                        uint32_t collection);
+    zx_status_t
+    DisplayControllerImplGetSingleBufferFramebuffer(zx::vmo* out_vmo,
+                                                    uint32_t* out_stride);
+
+private:
+    struct ColorBuffer {
+        uint32_t id = 0;
+        zx_paddr_t paddr = 0;
+        size_t size = 0;
+        uint32_t width = 0;
+        uint32_t height = 0;
+        zx::vmo vmo;
+        zx::pmt pmt;
+    };
+
+    static void OnSignal(void* ctx, int32_t flags);
+    void OnReadable();
+
+    void WriteLocked(uint32_t cmd_size) TA_REQ(lock_);
+    zx_status_t ReadResultLocked(uint32_t* result) TA_REQ(lock_);
+    zx_status_t ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result)
+        TA_REQ(lock_);
+    int32_t GetFbParamLocked(uint32_t param, int32_t default_value)
+        TA_REQ(lock_);
+    zx_status_t CreateColorBufferLocked(uint32_t width, uint32_t height,
+                                        uint32_t* id) TA_REQ(lock_);
+    void OpenColorBufferLocked(uint32_t id) TA_REQ(lock_);
+    void CloseColorBufferLocked(uint32_t id) TA_REQ(lock_);
+    zx_status_t UpdateColorBufferLocked(uint32_t id, zx_paddr_t paddr,
+                                        uint32_t width, uint32_t height,
+                                        size_t size, uint32_t* result)
+        TA_REQ(lock_);
+    void FbPostLocked(uint32_t id) TA_REQ(lock_);
+
+    int FlushHandler();
+
+    fbl::Mutex lock_;
+    fbl::ConditionVariable readable_cvar_;
+    ddk::GoldfishControlProtocolClient control_ TA_GUARDED(lock_);
+    ddk::GoldfishPipeProtocolClient pipe_ TA_GUARDED(lock_);
+    int32_t id_ = 0;
+    zx::bti bti_;
+    ddk::IoBuffer cmd_buffer_ TA_GUARDED(lock_);
+    ddk::IoBuffer io_buffer_ TA_GUARDED(lock_);
+
+    thrd_t flush_thread_{};
+    fbl::Mutex flush_lock_;
+    ColorBuffer* current_fb_ TA_GUARDED(flush_lock_) = nullptr;
+    ddk::DisplayControllerInterfaceProtocolClient
+        dc_intf_ TA_GUARDED(flush_lock_);
+    uint32_t width_ TA_GUARDED(flush_lock_) = 1024;
+    uint32_t height_ TA_GUARDED(flush_lock_) = 768;
+    bool shutdown_ TA_GUARDED(flush_lock_) = false;
+
+    DISALLOW_COPY_ASSIGN_AND_MOVE(Display);
+};
+
+} // namespace goldfish
+
+#endif // ZIRCON_SYSTEM_DEV_DISPLAY_GOLDFISH_DISPLAY_DISPLAY_H_