/* * 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 "VirtioGpuPipeStream.h" #include #include #include #include #include "VirtGpu.h" static const size_t kTransferBufferSize = (1048576); static const size_t kReadSize = 512 * 1024; static const size_t kWriteOffset = kReadSize; VirtioGpuPipeStream::VirtioGpuPipeStream(size_t bufSize) : IOStream(bufSize), m_virtio_mapped(nullptr), m_bufsize(bufSize), m_buf(nullptr), m_writtenPos(0) { } VirtioGpuPipeStream::VirtioGpuPipeStream(size_t bufSize, int fd) : IOStream(bufSize), m_fd(fd), m_virtio_mapped(nullptr), m_bufsize(bufSize), m_buf(nullptr), m_writtenPos(0) { } VirtioGpuPipeStream::~VirtioGpuPipeStream() { free(m_buf); } bool VirtioGpuPipeStream::valid() { return m_device != nullptr; } int VirtioGpuPipeStream::getRendernodeFd() { if (m_device == nullptr) { return -1; } return m_device->getDeviceHandle(); } int VirtioGpuPipeStream::connect(const char* serviceName) { if (!m_device) { m_device.reset(createPlatformVirtGpuDevice(kCapsetNone, m_fd)); if (!m_device) { ALOGE("Failed to create VirtioGpuPipeStream VirtGpuDevice."); return -1; } m_resource = m_device->createResource(kTransferBufferSize, 1, kTransferBufferSize, VIRGL_FORMAT_R8_UNORM, PIPE_BUFFER, VIRGL_BIND_CUSTOM); if (!m_resource) { ALOGE("Failed to create VirtioGpuPipeStream resource."); return -1; } m_resourceMapping = m_resource->createMapping(); if (!m_resourceMapping) { ALOGE("Failed to create VirtioGpuPipeStream resource mapping."); return -1; } m_virtio_mapped = m_resourceMapping->asRawPtr(); if (!m_virtio_mapped) { ALOGE("Failed to create VirtioGpuPipeStream resource mapping ptr."); return -1; } } wait(); if (serviceName) { writeFully(serviceName, strlen(serviceName) + 1); } else { static const char kPipeString[] = "pipe:opengles"; std::string pipeStr(kPipeString); writeFully(kPipeString, sizeof(kPipeString)); } return 0; } uint64_t VirtioGpuPipeStream::initProcessPipe() { connect("pipe:GLProcessPipe"); int32_t confirmInt = 100; writeFully(&confirmInt, sizeof(confirmInt)); uint64_t res; readFully(&res, sizeof(res)); return res; } void *VirtioGpuPipeStream::allocBuffer(size_t minSize) { size_t allocSize = (m_bufsize < minSize ? minSize : m_bufsize); if (!m_buf) { m_buf = (unsigned char *)malloc(allocSize); } else if (m_bufsize < allocSize) { unsigned char *p = (unsigned char *)realloc(m_buf, allocSize); if (p != NULL) { m_buf = p; m_bufsize = allocSize; } else { ERR("realloc (%zu) failed\n", allocSize); free(m_buf); m_buf = NULL; m_bufsize = 0; } } return m_buf; } int VirtioGpuPipeStream::commitBuffer(size_t size) { if (size == 0) return 0; return writeFully(m_buf, size); } int VirtioGpuPipeStream::writeFully(const void *buf, size_t len) { //DBG(">> VirtioGpuPipeStream::writeFully %d\n", 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("VirtioGpuPipeStream::writeFully failed, buf=NULL, len %zu," " lethal error, exiting", len); abort(); } return 0; } size_t res = len; int retval = 0; while (res > 0) { ssize_t stat = transferToHost((const char *)(buf) + (len - res), res); if (stat > 0) { res -= stat; continue; } if (stat == 0) { /* EOF */ ERR("VirtioGpuPipeStream::writeFully failed: premature EOF\n"); retval = -1; break; } if (errno == EAGAIN) { continue; } retval = stat; ERR("VirtioGpuPipeStream::writeFully failed: %s, lethal error, exiting.\n", strerror(errno)); abort(); } //DBG("<< VirtioGpuPipeStream::writeFully %d\n", len ); return retval; } const unsigned char *VirtioGpuPipeStream::readFully(void *buf, size_t len) { flush(); if (!valid()) return NULL; 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("VirtioGpuPipeStream::readFully failed, buf=NULL, len %zu, lethal" " error, exiting.", len); abort(); } } size_t res = len; while (res > 0) { ssize_t stat = transferFromHost((char *)(buf) + len - res, res); if (stat == 0) { // client shutdown; return NULL; } else if (stat < 0) { if (errno == EAGAIN) { continue; } else { ERR("VirtioGpuPipeStream::readFully failed (buf %p, len %zu" ", res %zu): %s, lethal error, exiting.", buf, len, res, strerror(errno)); abort(); } } else { res -= stat; } } //DBG("<< VirtioGpuPipeStream::readFully %d\n", len); return (const unsigned char *)buf; } const unsigned char *VirtioGpuPipeStream::commitBufferAndReadFully( size_t writeSize, void *userReadBufPtr, size_t totalReadSize) { return commitBuffer(writeSize) ? nullptr : readFully(userReadBufPtr, totalReadSize); } const unsigned char *VirtioGpuPipeStream::read( void *buf, size_t *inout_len) { //DBG(">> VirtioGpuPipeStream::read %d\n", *inout_len); if (!valid()) return NULL; if (!buf) { ERR("VirtioGpuPipeStream::read failed, buf=NULL"); return NULL; // do not allow NULL buf in that implementation } int n = recv(buf, *inout_len); if (n > 0) { *inout_len = n; return (const unsigned char *)buf; } //DBG("<< VirtioGpuPipeStream::read %d\n", *inout_len); return NULL; } int VirtioGpuPipeStream::recv(void *buf, size_t len) { if (!valid()) return -EINVAL; char* p = (char *)buf; int ret = 0; while(len > 0) { int res = transferFromHost(p, len); if (res > 0) { p += res; ret += res; len -= res; continue; } if (res == 0) { /* EOF */ break; } if (errno != EAGAIN) { continue; } /* A real error */ if (ret == 0) ret = -1; break; } return ret; } void VirtioGpuPipeStream::wait() { int ret = m_resource->wait(); if (ret) { ERR("VirtioGpuPipeStream: DRM_IOCTL_VIRTGPU_WAIT failed with %d (%s)\n", errno, strerror(errno)); } m_writtenPos = 0; } ssize_t VirtioGpuPipeStream::transferToHost(const void* buffer, size_t len) { size_t todo = len; size_t done = 0; int ret = EAGAIN; unsigned char* virtioPtr = m_virtio_mapped; const unsigned char* readPtr = reinterpret_cast(buffer); while (done < len) { size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo; if (toXfer > (kTransferBufferSize - m_writtenPos)) { wait(); } memcpy(virtioPtr + m_writtenPos, readPtr, toXfer); ret = m_resource->transferToHost(m_writtenPos, toXfer); if (ret) { ERR("VirtioGpuPipeStream: failed to transferToHost() with errno %d (%s)\n", errno, strerror(errno)); return (ssize_t)ret; } done += toXfer; readPtr += toXfer; todo -= toXfer; m_writtenPos += toXfer; } return len; } ssize_t VirtioGpuPipeStream::transferFromHost(void* buffer, size_t len) { size_t todo = len; size_t done = 0; int ret = EAGAIN; const unsigned char* virtioPtr = m_virtio_mapped; unsigned char* readPtr = reinterpret_cast(buffer); if (m_writtenPos) { wait(); } while (done < len) { size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo; ret = m_resource->transferFromHost(0, toXfer); if (ret) { ERR("VirtioGpuPipeStream: failed to transferFromHost() with errno %d (%s)\n", errno, strerror(errno)); return (ssize_t)ret; } wait(); memcpy(readPtr, virtioPtr, toXfer); done += toXfer; readPtr += toXfer; todo -= toXfer; } return len; }