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: