WIP: Add virtio-gpu backend to OpenglSystem.

This change switches device OpenGL emulation over to using virtio-gpu
instead of qemu pipes. The feature is enabled at compile time, and only
if BOARD_GPU_DRIVERS contains "virgl", which is used by other projects
to trigger support for virtio-gpu 3d.

Control stream writes are passed through the existing virtio-gpu 3d
ioctls, and they can be processed in batches, so long as no response is
required. Because responses cannot be sent from kernel->usermode in the
same way socket data can, a "response page" is configured, which is
written to by a qemu driver.

Bug: 77276633
Test: foo
Change-Id: I7ee8b6db815b35d708630dda417039b80d2dfddf
diff --git a/host/include/libOpenglRender/IOStream.h b/host/include/libOpenglRender/IOStream.h
index 1d32ea1..d06440e 100644
--- a/host/include/libOpenglRender/IOStream.h
+++ b/host/include/libOpenglRender/IOStream.h
@@ -41,7 +41,7 @@
         // NOTE: m_buf is 'owned' by the child class thus we expect it to be released by it
     }
 
-    unsigned char *alloc(size_t len) {
+    virtual unsigned char *alloc(size_t len) {
 
         if (m_buf && len > m_free) {
             if (flush() < 0) {
@@ -68,7 +68,7 @@
         return ptr;
     }
 
-    int flush() {
+    virtual int flush() {
 
         if (!m_buf || m_free == m_bufsize) return 0;
 
diff --git a/system/GLESv1/gl.cpp b/system/GLESv1/gl.cpp
index 6e07004..56e1630 100644
--- a/system/GLESv1/gl.cpp
+++ b/system/GLESv1/gl.cpp
@@ -20,7 +20,6 @@
 #include "GLES/gl.h"
 #include "GLES/glext.h"
 #include "ErrorLog.h"
-#include "gralloc_cb.h"
 #include "ThreadInfo.h"
 #include "EGLImage.h"
 
@@ -46,6 +45,11 @@
     if (!rcEnc) { \
         ALOGE("egl: Failed to get renderControl encoder context\n"); \
         return ret; \
+    } \
+    Gralloc *grallocHelper = hostCon->grallocHelper(); \
+    if (!grallocHelper) { \
+        ALOGE("egl: Failed to get grallocHelper\n"); \
+        return ret; \
     }
 
 //GL extensions
@@ -74,7 +78,7 @@
 
         ctx->override2DTextureTarget(target);
         rcEnc->rcBindTexture(rcEnc,
-                ((cb_handle_t *)(native_buffer->handle))->hostHandle);
+                grallocHelper->getHostHandle(native_buffer->handle));
         ctx->restore2DTextureTarget();
     }
     else if (image->target == EGL_GL_TEXTURE_2D_KHR) {
@@ -107,7 +111,8 @@
         }
 
         DEFINE_AND_VALIDATE_HOST_CONNECTION();
-        rcEnc->rcBindRenderbuffer(rcEnc, ((cb_handle_t *)(native_buffer->handle))->hostHandle);
+        rcEnc->rcBindRenderbuffer(rcEnc,
+                grallocHelper->getHostHandle(native_buffer->handle));
     } else {
         //TODO
     }
diff --git a/system/GLESv2/gl2.cpp b/system/GLESv2/gl2.cpp
index 331da78..4423300 100644
--- a/system/GLESv2/gl2.cpp
+++ b/system/GLESv2/gl2.cpp
@@ -20,7 +20,6 @@
 #include "GLES/gl.h"
 #include "GLES/glext.h"
 #include "ErrorLog.h"
-#include "gralloc_cb.h"
 #include "ThreadInfo.h"
 #include "EGLImage.h"
 
@@ -46,6 +45,11 @@
     if (!rcEnc) { \
         ALOGE("egl: Failed to get renderControl encoder context\n"); \
         return ret; \
+    } \
+    Gralloc *grallocHelper = hostCon->grallocHelper(); \
+    if (!grallocHelper) { \
+        ALOGE("egl: Failed to get grallocHelper\n"); \
+        return ret; \
     }
 
 //GL extensions
@@ -76,7 +80,8 @@
 
         ctx->override2DTextureTarget(target);
         ctx->associateEGLImage(target, hostImage);
-        rcEnc->rcBindTexture(rcEnc, ((cb_handle_t *)(native_buffer->handle))->hostHandle);
+        rcEnc->rcBindTexture(rcEnc,
+                grallocHelper->getHostHandle(native_buffer->handle));
         ctx->restore2DTextureTarget(target);
     }
     else if (image->target == EGL_GL_TEXTURE_2D_KHR) {
@@ -109,7 +114,8 @@
         }
 
         DEFINE_AND_VALIDATE_HOST_CONNECTION();
-        rcEnc->rcBindRenderbuffer(rcEnc, ((cb_handle_t *)(native_buffer->handle))->hostHandle);
+        rcEnc->rcBindRenderbuffer(rcEnc,
+                grallocHelper->getHostHandle(native_buffer->handle));
     } else {
         //TODO
     }
diff --git a/system/OpenglSystemCommon/Android.mk b/system/OpenglSystemCommon/Android.mk
index 9305119..7d34e67 100644
--- a/system/OpenglSystemCommon/Android.mk
+++ b/system/OpenglSystemCommon/Android.mk
@@ -11,6 +11,13 @@
     QemuPipeStream.cpp \
     ThreadInfo.cpp
 
+ifneq ($(filter virgl, $(BOARD_GPU_DRIVERS)),)
+LOCAL_CFLAGS += -DVIRTIO_GPU
+LOCAL_SRC_FILES += VirtioGpuStream.cpp
+LOCAL_C_INCLUDES += external/libdrm external/minigbm/cros_gralloc
+LOCAL_SHARED_LIBRARIES += libdrm
+endif
+
 ifdef IS_AT_LEAST_OPD1
 LOCAL_HEADER_LIBRARIES += libnativebase_headers
 
diff --git a/system/OpenglSystemCommon/HostConnection.cpp b/system/OpenglSystemCommon/HostConnection.cpp
index 34ddef4..f56e3f5 100644
--- a/system/OpenglSystemCommon/HostConnection.cpp
+++ b/system/OpenglSystemCommon/HostConnection.cpp
@@ -21,14 +21,47 @@
 #include "QemuPipeStream.h"
 #include "TcpStream.h"
 #include "ThreadInfo.h"
+#include "gralloc_cb.h"
+#ifdef VIRTIO_GPU
+#include "VirtioGpuStream.h"
+#endif
 
 #include <cutils/log.h>
 
 #define STREAM_BUFFER_SIZE  (4*1024*1024)
 #define STREAM_PORT_NUM     22468
 
-/* Set to 1 to use a QEMU pipe, or 0 for a TCP connection */
-#define  USE_QEMU_PIPE  1
+enum HostConnectionType {
+    HOST_CONNECTION_TCP = 0,
+    HOST_CONNECTION_QEMU_PIPE = 1,
+    HOST_CONNECTION_VIRTIO_GPU = 2,
+};
+
+class GoldfishGralloc : public Gralloc
+{
+public:
+    uint32_t getHostHandle(native_handle_t const* handle)
+    {
+        return ((cb_handle_t *)handle)->hostHandle;
+    }
+
+    int getFormat(native_handle_t const* handle)
+    {
+        return ((cb_handle_t *)handle)->format;
+    }
+};
+
+class GoldfishProcessPipe : public ProcessPipe
+{
+public:
+    bool processPipeInit(renderControl_encoder_context_t *rcEnc)
+    {
+        return ::processPipeInit(rcEnc);
+    }
+};
+
+static GoldfishGralloc m_goldfishGralloc;
+static GoldfishProcessPipe m_goldfishProcessPipe;
 
 HostConnection::HostConnection() :
     m_stream(NULL),
@@ -57,7 +90,7 @@
 HostConnection *HostConnection::getWithThreadInfo(EGLThreadInfo* tinfo) {
 
     /* TODO: Make this configurable with a system property */
-    const int useQemuPipe = USE_QEMU_PIPE;
+    const enum HostConnectionType connType = HOST_CONNECTION_VIRTIO_GPU;
 
     // Get thread info
     if (!tinfo) {
@@ -70,38 +103,66 @@
             return NULL;
         }
 
-        if (useQemuPipe) {
-            QemuPipeStream *stream = new QemuPipeStream(STREAM_BUFFER_SIZE);
-            if (!stream) {
-                ALOGE("Failed to create QemuPipeStream for host connection!!!\n");
-                delete con;
-                return NULL;
+        switch (connType) {
+            default:
+            case HOST_CONNECTION_QEMU_PIPE: {
+                QemuPipeStream *stream = new QemuPipeStream(STREAM_BUFFER_SIZE);
+                if (!stream) {
+                    ALOGE("Failed to create QemuPipeStream for host connection!!!\n");
+                    delete con;
+                    return NULL;
+                }
+                if (stream->connect() < 0) {
+                    ALOGE("Failed to connect to host (QemuPipeStream)!!!\n");
+                    delete stream;
+                    delete con;
+                    return NULL;
+                }
+                con->m_stream = stream;
+                con->m_pipeFd = stream->getSocket();
+                con->m_grallocHelper = &m_goldfishGralloc;
+                con->m_processPipe = &m_goldfishProcessPipe;
+                break;
             }
-            if (stream->connect() < 0) {
-                ALOGE("Failed to connect to host (QemuPipeStream)!!!\n");
-                delete stream;
-                delete con;
-                return NULL;
-            }
-            con->m_stream = stream;
-            con->m_pipeFd = stream->getSocket();
-        }
-        else /* !useQemuPipe */
-        {
-            TcpStream *stream = new TcpStream(STREAM_BUFFER_SIZE);
-            if (!stream) {
-                ALOGE("Failed to create TcpStream for host connection!!!\n");
-                delete con;
-                return NULL;
-            }
+            case HOST_CONNECTION_TCP: {
+                TcpStream *stream = new TcpStream(STREAM_BUFFER_SIZE);
+                if (!stream) {
+                    ALOGE("Failed to create TcpStream for host connection!!!\n");
+                    delete con;
+                    return NULL;
+                }
 
-            if (stream->connect("10.0.2.2", STREAM_PORT_NUM) < 0) {
-                ALOGE("Failed to connect to host (TcpStream)!!!\n");
-                delete stream;
-                delete con;
-                return NULL;
+                if (stream->connect("10.0.2.2", STREAM_PORT_NUM) < 0) {
+                    ALOGE("Failed to connect to host (TcpStream)!!!\n");
+                    delete stream;
+                    delete con;
+                    return NULL;
+                }
+                con->m_stream = stream;
+                con->m_grallocHelper = &m_goldfishGralloc;
+                con->m_processPipe = &m_goldfishProcessPipe;
+                break;
             }
-            con->m_stream = stream;
+#ifdef VIRTIO_GPU
+            case HOST_CONNECTION_VIRTIO_GPU: {
+                VirtioGpuStream *stream = new VirtioGpuStream(STREAM_BUFFER_SIZE);
+                if (!stream) {
+                    ALOGE("Failed to create VirtioGpu for host connection!!!\n");
+                    delete con;
+                    return NULL;
+                }
+                if (stream->connect() < 0) {
+                    ALOGE("Failed to connect to host (VirtioGpu)!!!\n");
+                    delete stream;
+                    delete con;
+                    return NULL;
+                }
+                con->m_stream = stream;
+                con->m_grallocHelper = stream->getGralloc();
+                con->m_processPipe = stream->getProcessPipe();
+                break;
+            }
+#endif
         }
 
         // send zero 'clientFlags' to the host.
@@ -161,7 +222,9 @@
         queryAndSetDmaImpl(m_rcEnc);
         queryAndSetGLESMaxVersion(m_rcEnc);
         queryAndSetNoErrorState(m_rcEnc);
-        processPipeInit(m_rcEnc);
+        if (m_processPipe) {
+            m_processPipe->processPipeInit(m_rcEnc);
+        }
     }
     return m_rcEnc;
 }
diff --git a/system/OpenglSystemCommon/HostConnection.h b/system/OpenglSystemCommon/HostConnection.h
index 2b49857..e3dbe80 100644
--- a/system/OpenglSystemCommon/HostConnection.h
+++ b/system/OpenglSystemCommon/HostConnection.h
@@ -21,6 +21,7 @@
 #include "ChecksumCalculator.h"
 #include "goldfish_dma.h"
 
+#include <cutils/native_handle.h>
 #include <string>
 
 class GLEncoder;
@@ -110,6 +111,21 @@
     GLESMaxVersion m_glesMaxVersion;
 };
 
+// Abstraction for gralloc handle conversion
+class Gralloc {
+public:
+    virtual uint32_t getHostHandle(native_handle_t const* handle) = 0;
+    virtual int getFormat(native_handle_t const* handle) = 0;
+    virtual ~Gralloc() {}
+};
+
+// Abstraction for process pipe helper
+class ProcessPipe {
+public:
+    virtual bool processPipeInit(renderControl_encoder_context_t *rcEnc) = 0;
+    virtual ~ProcessPipe() {}
+};
+
 struct EGLThreadInfo;
 
 class HostConnection
@@ -124,6 +140,7 @@
     GL2Encoder *gl2Encoder();
     ExtendedRCEncoderContext *rcEncoder();
     ChecksumCalculator *checksumHelper() { return &m_checksumHelper; }
+    Gralloc *grallocHelper() { return m_grallocHelper; }
 
     void flush() {
         if (m_stream) {
@@ -159,6 +176,8 @@
     GL2Encoder  *m_gl2Enc;
     ExtendedRCEncoderContext *m_rcEnc;
     ChecksumCalculator m_checksumHelper;
+    Gralloc *m_grallocHelper;
+    ProcessPipe *m_processPipe;
     std::string m_glExtensions;
     bool m_grallocOnly;
     int m_pipeFd;
diff --git a/system/OpenglSystemCommon/VirtioGpuStream.cpp b/system/OpenglSystemCommon/VirtioGpuStream.cpp
new file mode 100644
index 0000000..b637109
--- /dev/null
+++ b/system/OpenglSystemCommon/VirtioGpuStream.cpp
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtioGpuStream.h"
+
+#include <cros_gralloc_handle.h>
+#include <drm/virtgpu_drm.h>
+#include <xf86drm.h>
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include <cassert>
+#include <unistd.h>
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 0x1000
+#endif
+
+// In a virtual machine, there should only be one GPU
+#define RENDERNODE_MINOR 128
+
+// Maximum size of readback / response buffer in bytes
+#define MAX_CMDRESPBUF_SIZE (10*PAGE_SIZE)
+
+// Attributes use to allocate our response buffer
+// Similar to virgl's fence objects
+#define PIPE_BUFFER             0
+#define VIRGL_FORMAT_R8_UNORM   64
+#define VIRGL_BIND_CUSTOM       (1 << 17)
+
+// Conservative; see virgl_winsys.h
+#define VIRGL_MAX_CMDBUF_DWORDS (16*1024)
+#define VIRGL_MAX_CMDBUF_SIZE   (4*VIRGL_MAX_CMDBUF_DWORDS)
+
+struct VirtioGpuCmd {
+    uint32_t op;
+    uint32_t cmdSize;
+    unsigned char buf[0];
+} __attribute__((packed));
+
+uint32_t CrosGralloc::getHostHandle(native_handle_t const* handle_)
+{
+    uint32_t id = 0;
+
+    if (m_fd >= 0) {
+        cros_gralloc_handle const* handle =
+          reinterpret_cast<cros_gralloc_handle const*>(handle_);
+        drmPrimeFDToHandle(m_fd, handle->fds[0], &id);
+    }
+
+    return id;
+}
+
+int CrosGralloc::getFormat(native_handle_t const* handle)
+{
+    return ((cros_gralloc_handle *)handle)->droid_format;
+}
+
+bool VirtioGpuProcessPipe::processPipeInit(renderControl_encoder_context_t *rcEnc)
+{
+  union {
+      uint64_t proto;
+      struct {
+          int pid;
+          int tid;
+      } id;
+  } puid = {
+      .id.pid = getpid(),
+      .id.tid = gettid(),
+  };
+  rcEnc->rcSetPuid(rcEnc, puid.proto);
+  return true;
+}
+
+VirtioGpuStream::VirtioGpuStream(size_t bufSize) :
+    IOStream(0U),
+    m_fd(-1),
+    m_bufSize(bufSize),
+    m_buf(nullptr),
+    m_cmdResp_rh(0U),
+    m_cmdResp_bo(0U),
+    m_cmdResp(nullptr),
+    m_cmdRespPos(0U),
+    m_cmdPos(0U),
+    m_flushPos(0U),
+    m_allocSize(0U),
+    m_allocFlushSize(0U)
+{
+}
+
+VirtioGpuStream::~VirtioGpuStream()
+{
+    if (m_cmdResp) {
+        munmap(m_cmdResp, MAX_CMDRESPBUF_SIZE);
+    }
+
+    if (m_cmdResp_bo > 0U) {
+        drm_gem_close gem_close = {
+            .handle = m_cmdResp_bo,
+        };
+        drmIoctl(m_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
+    }
+
+    if (m_fd >= 0) {
+        close(m_fd);
+    }
+
+    free(m_buf);
+}
+
+int VirtioGpuStream::connect()
+{
+    if (m_fd < 0) {
+        m_fd = drmOpenRender(RENDERNODE_MINOR);
+        if (m_fd < 0) {
+            ERR("%s: failed with fd %d (%s)", __func__, m_fd, strerror(errno));
+            return -1;
+        }
+    }
+
+    if (!m_cmdResp_bo) {
+        drm_virtgpu_resource_create create = {
+            .target     = PIPE_BUFFER,
+            .format     = VIRGL_FORMAT_R8_UNORM,
+            .bind       = VIRGL_BIND_CUSTOM,
+            .width      = MAX_CMDRESPBUF_SIZE,
+            .height     = 1U,
+            .depth      = 1U,
+            .array_size = 0U,
+            .size       = MAX_CMDRESPBUF_SIZE,
+            .stride     = MAX_CMDRESPBUF_SIZE,
+        };
+        int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create);
+        if (ret) {
+            ERR("%s: failed with %d allocating command response buffer (%s)",
+                __func__, ret, strerror(errno));
+            return -1;
+        }
+        m_cmdResp_bo = create.bo_handle;
+        if (!m_cmdResp_bo) {
+            ERR("%s: no handle when allocating command response buffer",
+                __func__);
+            return -1;
+        }
+        m_cmdResp_rh = create.res_handle;
+        assert(create.size == MAX_CMDRESPBUF_SIZE && "Command response buffer wrongly sized");
+    }
+
+    if (!m_cmdResp) {
+        drm_virtgpu_map map = {
+            .handle = m_cmdResp_bo,
+        };
+        int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_MAP, &map);
+        if (ret) {
+            ERR("%s: failed with %d mapping command response buffer (%s)",
+                __func__, ret, strerror(errno));
+            return -1;
+        }
+        m_cmdResp = static_cast<VirtioGpuCmd *>(mmap64(nullptr,
+                                                       MAX_CMDRESPBUF_SIZE,
+                                                       PROT_READ, MAP_SHARED,
+                                                       m_fd, map.offset));
+        if (m_cmdResp == MAP_FAILED) {
+            ERR("%s: failed with %d mmap'ing command response buffer (%s)",
+                __func__, ret, strerror(errno));
+            return -1;
+        }
+    }
+
+    m_gralloc.setFd(m_fd);
+    return 0;
+}
+
+int VirtioGpuStream::flush()
+{
+    int ret = commitBuffer(m_allocSize - m_allocFlushSize);
+    if (ret)
+        return ret;
+    m_allocFlushSize = m_allocSize;
+    return 0;
+}
+
+void *VirtioGpuStream::allocBuffer(size_t minSize)
+{
+    if (m_buf) {
+        // Try to model the alloc() calls being made by the user. They should be
+        // obeying the protocol and using alloc() for anything they don't write
+        // with writeFully(), so we can know if this alloc() is for part of a
+        // command, or not. If it is not for part of a command, we are starting
+        // a new command, and should increment m_cmdPos.
+        VirtioGpuCmd *cmd = reinterpret_cast<VirtioGpuCmd *>(&m_buf[m_cmdPos]);
+        if (m_allocSize + minSize > cmd->cmdSize) {
+            m_allocFlushSize = 0U;
+            m_allocSize = 0U;
+            // This might also be a convenient point to flush commands
+            if (m_cmdPos + cmd->cmdSize + minSize > m_bufSize) {
+                if (commitAll() < 0) {
+                    ERR("%s: command flush failed", __func__);
+                    m_flushPos = 0U;
+                    m_bufSize = 0U;
+                    m_cmdPos = 0U;
+                    free(m_buf);
+                    m_buf = nullptr;
+                    return nullptr;
+                }
+            } else {
+                m_cmdPos += cmd->cmdSize;
+                m_flushPos = m_cmdPos;
+            }
+        }
+    }
+
+    // Update m_allocSize here, before minSize is tampered with below
+    m_allocSize += minSize;
+
+    // Make sure anything we already have written to the buffer is retained
+    minSize += m_flushPos;
+
+    size_t allocSize = (m_bufSize < minSize ? minSize : m_bufSize);
+    if (!m_buf) {
+        m_buf = static_cast<unsigned char *>(malloc(allocSize));
+    } else if (m_bufSize < allocSize) {
+        unsigned char *p = static_cast<unsigned char *>(realloc(m_buf, allocSize));
+        if (!p) {
+            free(m_buf);
+        }
+        m_buf = p;
+    }
+    if (!m_buf) {
+        ERR("%s: alloc (%zu) failed\n", __func__, allocSize);
+        m_allocFlushSize = 0U;
+        m_allocSize = 0U;
+        m_flushPos = 0U;
+        m_bufSize = 0U;
+        m_cmdPos = 0U;
+    } else {
+        m_bufSize = allocSize;
+    }
+    if (m_flushPos == 0 && m_cmdPos == 0) {
+      // During initialization, HostConnection will send an empty command
+      // packet to check the connection is good, but it doesn't obey the usual
+      // line protocol. This is a 4 byte write to [0], which is our 'op' field,
+      // and we don't have an op=0 so it's OK. We fake up a valid length, and
+      // overload this workaround by putting the res_handle for the readback
+      // buffer in the command payload, patched in just before we submit.
+      VirtioGpuCmd *cmd = reinterpret_cast<VirtioGpuCmd *>(&m_buf[m_cmdPos]);
+      cmd->op = 0U;
+      cmd->cmdSize = sizeof(*cmd) + sizeof(__u32);
+    }
+    return m_buf + m_cmdPos;
+}
+
+// For us, writeFully() means to write a command without any header, directly
+// into the buffer stream. We can use the packet frame written directly to the
+// stream to verify this write is within bounds, then update the counter.
+
+int VirtioGpuStream::writeFully(const void *buf, size_t len)
+{
+    if (!valid())
+        return -1;
+
+    if (!buf) {
+        if (len > 0) {
+            // If len is non-zero, buf must not be NULL. Otherwise the pipe would
+            // be in a corrupted state, which is lethal for the emulator.
+            ERR("%s: failed, buf=NULL, len %zu, lethal error, exiting",
+                __func__, len);
+            abort();
+        }
+        return 0;
+    }
+
+    VirtioGpuCmd *cmd = reinterpret_cast<VirtioGpuCmd *>(&m_buf[m_cmdPos]);
+
+    if (m_flushPos < sizeof(*cmd)) {
+        ERR("%s: writeFully len %zu would overwrite command header, "
+            "cmd_pos=%zu, flush_pos=%zu, lethal error, exiting", __func__,
+            len, m_cmdPos, m_flushPos);
+        abort();
+    }
+
+    if (m_flushPos + len > cmd->cmdSize) {
+        ERR("%s: writeFully len %zu would overflow the command bounds, "
+            "cmd_pos=%zu, flush_pos=%zu, cmdsize=%zu, lethal error, exiting",
+            __func__, len, m_cmdPos, m_flushPos, cmd->cmdSize);
+        abort();
+    }
+
+    if (len > VIRGL_MAX_CMDBUF_SIZE) {
+        ERR("%s: Large command (%zu bytes) exceeds virgl limits",
+            __func__, len);
+        /* Fall through */
+    }
+
+    memcpy(&m_buf[m_flushPos], buf, len);
+    commitBuffer(len);
+    m_allocSize += len;
+    return 0;
+}
+
+const unsigned char *VirtioGpuStream::readFully(void *buf, size_t len)
+{
+    if (!valid())
+        return nullptr;
+
+    if (!buf) {
+        if (len > 0) {
+            // If len is non-zero, buf must not be NULL. Otherwise the pipe would
+            // be in a corrupted state, which is lethal for the emulator.
+            ERR("%s: failed, buf=NULL, len %zu, lethal error, exiting.",
+                __func__, len);
+            abort();
+        }
+        return nullptr;
+    }
+
+    // Read is too big for current architecture
+    if (len > MAX_CMDRESPBUF_SIZE - sizeof(*m_cmdResp)) {
+        ERR("%s: failed, read too large, len %zu, lethal error, exiting.",
+            __func__, len);
+        abort();
+    }
+
+    // Commit all outstanding write commands (if any)
+    if (commitAll() < 0) {
+        ERR("%s: command flush failed", __func__);
+        return nullptr;
+    }
+
+    if (len > 0U && m_cmdRespPos == 0U) {
+        // When we are about to read for the first time, wait for the virtqueue
+        // to drain to this command, otherwise the data could be stale
+        drm_virtgpu_3d_wait wait = {
+            .handle = m_cmdResp_bo,
+        };
+        int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_WAIT, &wait);
+        if (ret) {
+            ERR("%s: failed with %d waiting for response buffer (%s)",
+                __func__, ret, strerror(errno));
+            // Fall through, hope for the best
+        }
+    }
+
+    // Most likely a protocol implementation error
+    if (m_cmdResp->cmdSize - sizeof(*m_cmdResp) < m_cmdRespPos + len) {
+        ERR("%s: failed, op %zu, len %zu, cmdSize %zu, pos %zu, lethal "
+            "error, exiting.", __func__, m_cmdResp->op, len,
+            m_cmdResp->cmdSize, m_cmdRespPos);
+        abort();
+    }
+
+    memcpy(buf, &m_cmdResp->buf[m_cmdRespPos], len);
+
+    if (m_cmdRespPos + len == m_cmdResp->cmdSize - sizeof(*m_cmdResp)) {
+        m_cmdRespPos = 0U;
+    } else {
+        m_cmdRespPos += len;
+    }
+
+    return reinterpret_cast<const unsigned char *>(buf);
+}
+
+int VirtioGpuStream::commitBuffer(size_t size)
+{
+    if (m_flushPos + size > m_bufSize) {
+        ERR("%s: illegal commit size %zu, flushPos %zu, bufSize %zu",
+            __func__, size, m_flushPos, m_bufSize);
+        return -1;
+    }
+    m_flushPos += size;
+    return 0;
+}
+
+int VirtioGpuStream::commitAll()
+{
+    size_t pos = 0U, numFlushed = 0U;
+    while (pos < m_flushPos) {
+        VirtioGpuCmd *cmd = reinterpret_cast<VirtioGpuCmd *>(&m_buf[pos]);
+
+        // Should never happen
+        if (pos + cmd->cmdSize > m_bufSize) {
+            ERR("%s: failed, pos %zu, cmdSize %zu, bufSize %zu, lethal "
+                "error, exiting.", __func__, pos, cmd->cmdSize, m_bufSize);
+            abort();
+        }
+
+        // Saw dummy command; patch it with res handle
+        if (cmd->op == 0) {
+            *(uint32_t *)cmd->buf = m_cmdResp_rh;
+        }
+
+        // Flush a single command
+        drm_virtgpu_execbuffer execbuffer = {
+            .size           = cmd->cmdSize,
+            .command        = reinterpret_cast<__u64>(cmd),
+            .bo_handles     = reinterpret_cast<__u64>(&m_cmdResp_bo),
+            .num_bo_handles = 1U,
+        };
+        int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_EXECBUFFER, &execbuffer);
+        if (ret) {
+            ERR("%s: failed with %d executing command buffer (%s)",  __func__,
+                ret, strerror(errno));
+            return -1;
+        }
+
+        pos += cmd->cmdSize;
+        numFlushed++;
+    }
+
+    if (pos > m_flushPos) {
+        ERR("%s: aliasing, flushPos %zu, pos %zu, probably ok", __func__,
+            m_flushPos, pos);
+        /* Fall through */
+    }
+
+    m_flushPos = 0U;
+    m_cmdPos = 0U;
+    return 0;
+}
diff --git a/system/OpenglSystemCommon/VirtioGpuStream.h b/system/OpenglSystemCommon/VirtioGpuStream.h
new file mode 100644
index 0000000..9d6faa5
--- /dev/null
+++ b/system/OpenglSystemCommon/VirtioGpuStream.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "HostConnection.h"
+#include "IOStream.h"
+
+#include <stdlib.h>
+
+/* This file implements an IOStream that uses VIRTGPU_EXECBUFFER ioctls on a
+ * virtio-gpu DRM rendernode device to communicate with the host.
+ */
+
+struct VirtioGpuCmd;
+
+class CrosGralloc : public Gralloc
+{
+    friend class VirtioGpuStream;
+
+public:
+    virtual uint32_t getHostHandle(native_handle_t const* handle);
+    virtual int getFormat(native_handle_t const* handle);
+
+private:
+    inline void setFd(int fd) { m_fd = fd; }
+    int m_fd = -1;
+};
+
+class VirtioGpuProcessPipe : public ProcessPipe
+{
+public:
+    virtual bool processPipeInit(renderControl_encoder_context_t *rcEnc);
+};
+
+class VirtioGpuStream : public IOStream
+{
+public:
+    explicit VirtioGpuStream(size_t bufSize);
+    ~VirtioGpuStream();
+
+    int connect();
+    Gralloc *getGralloc() { return &m_gralloc; }
+    ProcessPipe *getProcessPipe() { return &m_processPipe; }
+
+    // override IOStream so we can see non-rounded allocation sizes
+    virtual unsigned char *alloc(size_t len)
+    {
+        return static_cast<unsigned char *>(allocBuffer(len));
+    }
+
+    // override IOStream so we can model the caller's writes
+    virtual int flush();
+
+    virtual void *allocBuffer(size_t minSize);
+    virtual int writeFully(const void *buf, size_t len);
+    virtual const unsigned char *readFully(void *buf, size_t len);
+    virtual int commitBuffer(size_t size);
+    virtual const unsigned char *read(void *buf, size_t *inout_len) final
+    {
+        return readFully(buf, *inout_len);
+    }
+
+    bool valid()
+    {
+        return m_fd >= 0 && m_cmdResp_bo > 0 && m_cmdResp;
+    }
+
+private:
+    // rendernode fd
+    int m_fd;
+
+    // command memory buffer
+    size_t m_bufSize;
+    unsigned char *m_buf;
+
+    // response buffer res handle
+    uint32_t m_cmdResp_rh;
+
+    // response buffer ttm buffer object
+    uint32_t m_cmdResp_bo;
+
+    // user mapping of response buffer object
+    VirtioGpuCmd *m_cmdResp;
+
+    // byte offset to read cursor for last response
+    size_t m_cmdRespPos;
+
+    // byte offset to command being assembled
+    size_t m_cmdPos;
+
+    // byte offset to flush cursor
+    size_t m_flushPos;
+
+    // byte counter of allocs since last command boundary
+    size_t m_allocSize;
+
+    // bytes of an alloc flushed through flush() API
+    size_t m_allocFlushSize;
+
+    // CrOS gralloc interface
+    CrosGralloc m_gralloc;
+
+    // Fake process pipe implementation
+    VirtioGpuProcessPipe m_processPipe;
+
+    // commits all commands, resets buffer offsets
+    int commitAll();
+};
diff --git a/system/egl/egl.cpp b/system/egl/egl.cpp
index 16285f9..cd3620a 100644
--- a/system/egl/egl.cpp
+++ b/system/egl/egl.cpp
@@ -23,7 +23,6 @@
 #include <cutils/log.h>
 #include <cutils/properties.h>
 #include "goldfish_sync.h"
-#include "gralloc_cb.h"
 #include "GLClientState.h"
 #include "GLSharedGroup.h"
 #include "eglContext.h"
@@ -145,6 +144,11 @@
     if (!rcEnc) { \
         ALOGE("egl: Failed to get renderControl encoder context\n"); \
         return ret; \
+    } \
+    Gralloc *grallocHelper = hostCon->grallocHelper(); \
+    if (!grallocHelper) { \
+        ALOGE("egl: Failed to get grallocHelper\n"); \
+        return ret; \
     }
 
 #define DEFINE_AND_VALIDATE_HOST_CONNECTION_FOR_TLS(ret, tls) \
@@ -157,6 +161,11 @@
     if (!rcEnc) { \
         ALOGE("egl: Failed to get renderControl encoder context\n"); \
         return ret; \
+    } \
+    Gralloc const* grallocHelper = hostCon->grallocHelper(); \
+    if (!grallocHelper) { \
+        ALOGE("egl: Failed to get grallocHelper\n"); \
+        return ret; \
     }
 
 #define VALIDATE_CONTEXT_RETURN(context,ret)  \
@@ -401,7 +410,7 @@
         return EGL_FALSE;
     }
     rcEnc->rcSetWindowColorBuffer(rcEnc, rcSurface,
-            ((cb_handle_t*)(buffer->handle))->hostHandle);
+            grallocHelper->getHostHandle(buffer->handle));
 
     return EGL_TRUE;
 }
@@ -560,7 +569,7 @@
 #endif
 
     rcEnc->rcSetWindowColorBuffer(rcEnc, rcSurface,
-            ((cb_handle_t *)(buffer->handle))->hostHandle);
+            grallocHelper->getHostHandle(buffer->handle));
 
     setWidth(buffer->width);
     setHeight(buffer->height);
@@ -1950,9 +1959,9 @@
         if (native_buffer->common.version != sizeof(android_native_buffer_t))
             setErrorReturn(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
 
-        cb_handle_t *cb = (cb_handle_t *)(native_buffer->handle);
-
-        switch (cb->format) {
+        DEFINE_AND_VALIDATE_HOST_CONNECTION(EGL_FALSE);
+        int format = grallocHelper->getFormat(native_buffer->handle);
+        switch (format) {
             case HAL_PIXEL_FORMAT_RGBA_8888:
             case HAL_PIXEL_FORMAT_RGBX_8888:
             case HAL_PIXEL_FORMAT_RGB_888: