gfxstream: guest: introduce GfxStreamConnectionManager

The HostConnection class pulls in:

  - GLES_v1 encoder
  - GLES_v2 encoder
  - renderControl encoder
  - vulkan encoder

Since HostConnection is required to talk the host, that
means the libgfxstream_vulkan.so has a GLES dependency.

This is unnecessary.  Ideally, libgfxstream_vulkan.so
only needs the Vulkan encoder dependency, and that is
the case for virtgpu.

For Goldfish, since it uses the "process pipe"
initialization logic is used, for Android builds
a renderControl encoder dependency is still required.

To increase the separation of these encoders, this
change adds a GfxStreamConnectionManager class.  It
has no explicit dependency on any API encoder, but
can store thread-local instances of them.

The HostConnection class may be implemented in terms
of the GfxStreamConnectionManager API.  For now, the
plan is just to convert libgfxstream_vulkan to use
the ConnectionManager.

Reviewed-by: Aaron Ruby <aruby@blackberry.com>
Acked-by: Yonggang Luo <luoyonggang@gmail.com>
Acked-by: Adam Jackson <ajax@redhat.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/27246>
This commit is contained in:
Gurchetan Singh
2024-08-08 11:04:14 -07:00
committed by Marge Bot
parent e7116abf90
commit de7095ba5b
9 changed files with 987 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include "GfxStreamConnection.h"
GfxStreamConnection::GfxStreamConnection() {}
GfxStreamConnection::~GfxStreamConnection() {}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef GFXSTREAM_CONNECTION_H
#define GFXSTREAM_CONNECTION_H
class GfxStreamConnection {
public:
GfxStreamConnection();
virtual ~GfxStreamConnection();
virtual void* getEncoder() = 0;
};
#endif

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include "GfxStreamConnectionManager.h"
#include <cerrno>
#include "GoldfishAddressSpaceStream.h"
#include "QemuPipeStream.h"
#include "VirtGpu.h"
#include "VirtioGpuAddressSpaceStream.h"
#include "VirtioGpuPipeStream.h"
#include "util/log.h"
#define STREAM_BUFFER_SIZE (4 * 1024 * 1024)
struct ThreadInfo {
std::unique_ptr<GfxStreamConnectionManager> mgr;
};
static thread_local ThreadInfo sThreadInfo;
GfxStreamConnectionManager::GfxStreamConnectionManager(GfxStreamTransportType type,
VirtGpuCapset capset)
: mTransportType(type), mCapset(capset) {}
GfxStreamConnectionManager::~GfxStreamConnectionManager() {}
bool GfxStreamConnectionManager::initialize() {
switch (mTransportType) {
case GFXSTREAM_TRANSPORT_ADDRESS_SPACE: {
mStream = createGoldfishAddressSpaceStream(STREAM_BUFFER_SIZE);
if (!mStream) {
mesa_loge("Failed to create AddressSpaceStream for host connection\n");
return false;
}
break;
}
case GFXSTREAM_TRANSPORT_QEMU_PIPE: {
mStream = new QemuPipeStream(STREAM_BUFFER_SIZE);
if (mStream->connect() < 0) {
mesa_loge("Failed to connect to host (QemuPipeStream)\n");
return false;
}
break;
}
case GFXSTREAM_TRANSPORT_VIRTIO_GPU_PIPE: {
VirtioGpuPipeStream* pipeStream =
new VirtioGpuPipeStream(STREAM_BUFFER_SIZE, INVALID_DESCRIPTOR);
if (!pipeStream) {
mesa_loge("Failed to create VirtioGpu for host connection\n");
return false;
}
if (pipeStream->connect() < 0) {
mesa_loge("Failed to connect to host (VirtioGpu)\n");
return false;
}
mDescriptor = pipeStream->getRendernodeFd();
VirtGpuDevice::getInstance(mCapset);
mStream = pipeStream;
break;
}
case GFXSTREAM_TRANSPORT_VIRTIO_GPU_ADDRESS_SPACE: {
// Use kCapsetGfxStreamVulkan for now, Ranchu HWC needs to be modified to pass in
// right capset.
auto device = VirtGpuDevice::getInstance(kCapsetGfxStreamVulkan);
mDescriptor = device->getDeviceHandle();
mStream = createVirtioGpuAddressSpaceStream(kCapsetGfxStreamVulkan);
if (!mStream) {
mesa_loge("Failed to create virtgpu AddressSpaceStream\n");
return false;
}
break;
}
default:
return false;
}
// send zero 'clientFlags' to the host. This is actually part of the gfxstream protocol.
unsigned int* pClientFlags = (unsigned int*)mStream->allocBuffer(sizeof(unsigned int));
*pClientFlags = 0;
mStream->commitBuffer(sizeof(unsigned int));
return true;
}
GfxStreamConnectionManager* GfxStreamConnectionManager::getThreadLocalInstance(
GfxStreamTransportType type, VirtGpuCapset capset) {
if (sThreadInfo.mgr == nullptr) {
sThreadInfo.mgr = std::make_unique<GfxStreamConnectionManager>(type, capset);
if (!sThreadInfo.mgr->initialize()) {
sThreadInfo.mgr = nullptr;
return nullptr;
}
}
return sThreadInfo.mgr.get();
}
void GfxStreamConnectionManager::threadLocalExit() {
if (sThreadInfo.mgr == nullptr) {
return;
}
sThreadInfo.mgr.reset();
}
int32_t GfxStreamConnectionManager::addConnection(GfxStreamConnectionType type,
std::unique_ptr<GfxStreamConnection> connection) {
if (mConnections.find(type) != mConnections.end()) {
return -EINVAL;
}
mConnections[type] = std::move(connection);
return 0;
}
void* GfxStreamConnectionManager::getEncoder(GfxStreamConnectionType type) {
auto iterator = mConnections.find(type);
if (iterator == mConnections.end()) {
return nullptr;
}
return iterator->second->getEncoder();
}
gfxstream::guest::IOStream* GfxStreamConnectionManager::getStream() { return mStream; }
gfxstream::guest::IOStream* GfxStreamConnectionManager::processPipeStream(
GfxStreamTransportType transportType) {
switch (transportType) {
case GFXSTREAM_TRANSPORT_ADDRESS_SPACE:
case GFXSTREAM_TRANSPORT_QEMU_PIPE:
return new QemuPipeStream(STREAM_BUFFER_SIZE);
case GFXSTREAM_TRANSPORT_VIRTIO_GPU_ADDRESS_SPACE:
case GFXSTREAM_TRANSPORT_VIRTIO_GPU_PIPE:
return new VirtioGpuPipeStream(STREAM_BUFFER_SIZE, mDescriptor);
default:
return nullptr;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef GFXSTREAM_CONNECTION_MANAGER_H
#define GFXSTREAM_CONNECTION_MANAGER_H
#include <memory>
#include <unordered_map>
#include "GfxStreamConnection.h"
#include "VirtGpu.h"
#include "gfxstream/guest/IOStream.h"
enum GfxStreamConnectionType {
GFXSTREAM_CONNECTION_GLES = 1,
GFXSTREAM_CONNECTION_GLES2 = 2,
GFXSTREAM_CONNECTION_RENDER_CONTROL = 3,
GFXSTREAM_CONNECTION_VULKAN = 4,
};
enum GfxStreamTransportType {
GFXSTREAM_TRANSPORT_QEMU_PIPE = 1,
GFXSTREAM_TRANSPORT_ADDRESS_SPACE = 2,
GFXSTREAM_TRANSPORT_VIRTIO_GPU_PIPE = 3,
GFXSTREAM_TRANSPORT_VIRTIO_GPU_ADDRESS_SPACE = 4,
};
class GfxStreamConnectionManager {
public:
GfxStreamConnectionManager(GfxStreamTransportType type, VirtGpuCapset capset);
~GfxStreamConnectionManager();
static GfxStreamConnectionManager* getThreadLocalInstance(GfxStreamTransportType type,
VirtGpuCapset capset);
void threadLocalExit();
bool initialize();
int32_t addConnection(GfxStreamConnectionType type,
std::unique_ptr<GfxStreamConnection> connection);
void* getEncoder(GfxStreamConnectionType type);
gfxstream::guest::IOStream* getStream();
gfxstream::guest::IOStream* processPipeStream(GfxStreamTransportType transportType);
private:
// intrusively refcounted
gfxstream::guest::IOStream* mStream = nullptr;
int32_t mDescriptor = -1;
GfxStreamTransportType mTransportType;
VirtGpuCapset mCapset;
std::unordered_map<GfxStreamConnectionType, std::unique_ptr<GfxStreamConnection>> mConnections;
};
#endif

View File

@@ -0,0 +1,263 @@
/*
* Copyright (C) 2011 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 "QemuPipeStream.h"
#include <errno.h>
#include <qemu_pipe_bp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "util/log.h"
static const size_t kReadSize = 512 * 1024;
static const size_t kWriteOffset = kReadSize;
QemuPipeStream::QemuPipeStream(size_t bufSize)
: IOStream(bufSize),
m_sock((QEMU_PIPE_HANDLE)(-1)),
m_bufsize(bufSize),
m_buf(NULL),
m_read(0),
m_readLeft(0) {}
QemuPipeStream::QemuPipeStream(QEMU_PIPE_HANDLE sock, size_t bufSize)
: IOStream(bufSize), m_sock(sock), m_bufsize(bufSize), m_buf(NULL), m_read(0), m_readLeft(0) {}
QemuPipeStream::~QemuPipeStream() {
if (valid()) {
flush();
qemu_pipe_close(m_sock);
}
if (m_buf != NULL) {
free(m_buf);
}
}
int QemuPipeStream::connect(const char* serviceName) {
m_sock = qemu_pipe_open("opengles");
if (!valid()) {
mesa_loge("%s: failed to connect to opengles pipe", __FUNCTION__);
qemu_pipe_print_error(m_sock);
return -1;
}
return 0;
}
uint64_t QemuPipeStream::processPipeInit() {
QEMU_PIPE_HANDLE processPipe = qemu_pipe_open("GLProcessPipe");
uint64_t procUID = 0;
if (!qemu_pipe_valid(processPipe)) {
processPipe = 0;
mesa_logi("Process pipe failed");
return 0;
}
// Send a confirmation int to the host
int32_t confirmInt = 100;
if (qemu_pipe_write_fully(processPipe, &confirmInt, sizeof(confirmInt))) { // failed
qemu_pipe_close(processPipe);
processPipe = 0;
mesa_logi("Process pipe failed");
return 0;
}
// Ask the host for per-process unique ID
if (qemu_pipe_read_fully(processPipe, &procUID, sizeof(procUID))) {
qemu_pipe_close(processPipe);
processPipe = 0;
procUID = 0;
mesa_logi("Process pipe failed");
return 0;
}
return procUID;
}
void* QemuPipeStream::allocBuffer(size_t minSize) {
// Add dedicated read buffer space at the front of the buffer.
minSize += kReadSize;
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 {
mesa_loge("realloc (%zu) failed\n", allocSize);
free(m_buf);
m_buf = NULL;
m_bufsize = 0;
}
}
return m_buf + kWriteOffset;
};
int QemuPipeStream::commitBuffer(size_t size) {
if (size == 0) return 0;
return writeFully(m_buf + kWriteOffset, size);
}
int QemuPipeStream::writeFully(const void* buf, size_t len) {
return qemu_pipe_write_fully(m_sock, buf, len);
}
QEMU_PIPE_HANDLE QemuPipeStream::getSocket() const { return m_sock; }
const unsigned char* QemuPipeStream::readFully(void* buf, size_t len) {
return commitBufferAndReadFully(0, buf, len);
}
const unsigned char* QemuPipeStream::commitBufferAndReadFully(size_t writeSize,
void* userReadBufPtr,
size_t totalReadSize) {
unsigned char* userReadBuf = static_cast<unsigned char*>(userReadBufPtr);
if (!valid()) return NULL;
if (!userReadBuf) {
if (totalReadSize > 0) {
mesa_loge(
"QemuPipeStream::commitBufferAndReadFully failed, userReadBuf=NULL, totalReadSize "
"%zu, lethal"
" error, exiting.",
totalReadSize);
abort();
}
if (!writeSize) {
return NULL;
}
}
// Advance buffered read if not yet consumed.
size_t remaining = totalReadSize;
size_t bufferedReadSize = m_readLeft < remaining ? m_readLeft : remaining;
if (bufferedReadSize) {
memcpy(userReadBuf, m_buf + (m_read - m_readLeft), bufferedReadSize);
remaining -= bufferedReadSize;
m_readLeft -= bufferedReadSize;
}
// Early out if nothing left to do.
if (!writeSize && !remaining) {
return userReadBuf;
}
writeFully(m_buf + kWriteOffset, writeSize);
// Now done writing. Early out if no reading left to do.
if (!remaining) {
return userReadBuf;
}
// Read up to kReadSize bytes if all buffered read has been consumed.
size_t maxRead = m_readLeft ? 0 : kReadSize;
ssize_t actual = 0;
if (maxRead) {
actual = qemu_pipe_read(m_sock, m_buf, maxRead);
// Updated buffered read size.
if (actual > 0) {
m_read = m_readLeft = actual;
}
if (actual == 0) {
mesa_logi("%s: end of pipe", __FUNCTION__);
return NULL;
}
}
// Consume buffered read and read more if necessary.
while (remaining) {
bufferedReadSize = m_readLeft < remaining ? m_readLeft : remaining;
if (bufferedReadSize) {
memcpy(userReadBuf + (totalReadSize - remaining), m_buf + (m_read - m_readLeft),
bufferedReadSize);
remaining -= bufferedReadSize;
m_readLeft -= bufferedReadSize;
continue;
}
actual = qemu_pipe_read(m_sock, m_buf, kReadSize);
if (actual == 0) {
mesa_logi("%s: Failed reading from pipe: %d", __FUNCTION__, errno);
return NULL;
}
if (actual > 0) {
m_read = m_readLeft = actual;
continue;
}
if (!qemu_pipe_try_again(actual)) {
mesa_logi("%s: Error reading from pipe: %d", __FUNCTION__, errno);
return NULL;
}
}
return userReadBuf;
}
const unsigned char* QemuPipeStream::read(void* buf, size_t* inout_len) {
if (!valid()) return NULL;
if (!buf) {
mesa_loge("QemuPipeStream::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;
}
return NULL;
}
int QemuPipeStream::recv(void* buf, size_t len) {
if (!valid()) return int(ERR_INVALID_SOCKET);
char* p = (char*)buf;
int ret = 0;
while (len > 0) {
int res = qemu_pipe_read(m_sock, p, len);
if (res > 0) {
p += res;
ret += res;
len -= res;
continue;
}
if (res == 0) { /* EOF */
break;
}
if (qemu_pipe_try_again(res)) {
continue;
}
/* A real error */
if (ret == 0) ret = -1;
break;
}
return ret;
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2011 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.
*/
#ifndef __QEMU_PIPE_STREAM_H
#define __QEMU_PIPE_STREAM_H
/* This file implements an IOStream that uses a QEMU fast-pipe
* to communicate with the emulator's 'opengles' service. See
* <hardware/qemu_pipe.h> for more details.
*/
#include <qemu_pipe_bp.h>
#include <stdlib.h>
#include <memory>
#include "gfxstream/guest/IOStream.h"
class QemuPipeStream : public gfxstream::guest::IOStream {
public:
typedef enum { ERR_INVALID_SOCKET = -1000 } QemuPipeStreamError;
explicit QemuPipeStream(size_t bufsize = 10000);
~QemuPipeStream();
virtual int connect(const char* serviceName = nullptr);
virtual uint64_t processPipeInit();
virtual void* allocBuffer(size_t minSize);
virtual int commitBuffer(size_t size);
virtual const unsigned char* readFully(void* buf, size_t len);
virtual const unsigned char* commitBufferAndReadFully(size_t size, void* buf, size_t len);
virtual const unsigned char* read(void* buf, size_t* inout_len);
bool valid() { return qemu_pipe_valid(m_sock); }
int recv(void* buf, size_t len);
virtual int writeFully(const void* buf, size_t len);
QEMU_PIPE_HANDLE getSocket() const;
private:
QEMU_PIPE_HANDLE m_sock;
size_t m_bufsize;
unsigned char* m_buf;
size_t m_read;
size_t m_readLeft;
QemuPipeStream(QEMU_PIPE_HANDLE sock, size_t bufSize);
};
#endif

View File

@@ -0,0 +1,341 @@
/*
* 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 <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include "VirtGpu.h"
#include "util/log.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, int32_t descriptor)
: IOStream(bufSize),
m_fd(descriptor),
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) {
mesa_loge("Failed to create VirtioGpuPipeStream VirtGpuDevice.");
return -1;
}
m_resource = m_device->createResource(/*width=*/kTransferBufferSize,
/*height=*/1,
/*stride=*/kTransferBufferSize,
/*size=*/kTransferBufferSize, VIRGL_FORMAT_R8_UNORM,
PIPE_BUFFER, VIRGL_BIND_CUSTOM);
if (!m_resource) {
mesa_loge("Failed to create VirtioGpuPipeStream resource.");
return -1;
}
m_resourceMapping = m_resource->createMapping();
if (!m_resourceMapping) {
mesa_loge("Failed to create VirtioGpuPipeStream resource mapping.");
return -1;
}
m_virtio_mapped = m_resourceMapping->asRawPtr();
if (!m_virtio_mapped) {
mesa_loge("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::processPipeInit() {
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 {
mesa_loge("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.
mesa_loge(
"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 */
mesa_loge("VirtioGpuPipeStream::writeFully failed: premature EOF\n");
retval = -1;
break;
}
if (errno == EAGAIN) {
continue;
}
retval = stat;
mesa_loge("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.
mesa_loge(
"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 {
mesa_loge(
"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) {
mesa_loge("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) {
mesa_loge("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<const unsigned char*>(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) {
mesa_loge("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<unsigned char*>(buffer);
if (m_writtenPos) {
wait();
}
while (done < len) {
size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
ret = m_resource->transferFromHost(0, toXfer);
if (ret) {
mesa_loge("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;
}

View File

@@ -0,0 +1,68 @@
/*
* 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 <stdlib.h>
#include "VirtGpu.h"
#include "gfxstream/guest/IOStream.h"
/* This file implements an IOStream that uses VIRTGPU TRANSFER* ioctls on a
* virtio-gpu DRM rendernode device to communicate with a goldfish-pipe
* service on the host side.
*/
class VirtioGpuPipeStream : public gfxstream::guest::IOStream {
public:
explicit VirtioGpuPipeStream(size_t bufsize, int32_t descriptor);
~VirtioGpuPipeStream();
virtual int connect(const char* serviceName = nullptr);
virtual uint64_t processPipeInit();
virtual void* allocBuffer(size_t minSize);
virtual int commitBuffer(size_t size);
virtual const unsigned char* readFully(void* buf, size_t len);
virtual const unsigned char* commitBufferAndReadFully(size_t size, void* buf, size_t len);
virtual const unsigned char* read(void* buf, size_t* inout_len);
bool valid();
int getRendernodeFd();
int recv(void* buf, size_t len);
virtual int writeFully(const void* buf, size_t len);
private:
// sync. Also resets the write position.
void wait();
// transfer to/from host ops
ssize_t transferToHost(const void* buffer, size_t len);
ssize_t transferFromHost(void* buffer, size_t len);
int32_t m_fd = -1;
std::unique_ptr<VirtGpuDevice> m_device;
VirtGpuResourcePtr m_resource;
VirtGpuResourceMappingPtr m_resourceMapping;
unsigned char* m_virtio_mapped; // user mapping of bo
// intermediate buffer
size_t m_bufsize;
unsigned char* m_buf;
size_t m_writtenPos;
};

View File

@@ -0,0 +1,24 @@
# Copyright 2024 Android Open Source Project
# SPDX-License-Identifier: MIT
inc_connection_manager = include_directories('.')
files_libconnection_manager = files(
'GfxStreamConnectionManager.cpp',
'GfxStreamConnection.cpp',
'QemuPipeStream.cpp',
'VirtioGpuPipeStream.cpp',
)
libconnection_manager = static_library(
'connection_manager',
files_libconnection_manager,
cpp_args: gfxstream_guest_args,
include_directories: [inc_connection_manager, inc_guest_iostream,
inc_guest_iostream, inc_qemu_pipe,
inc_qemu_pipe_types, inc_goldfish_address_space,
inc_platform_virtgpu, inc_src],
link_with: [lib_guest_iostream, lib_goldfish_address_space, lib_qemu_pipe,
libplatform_virtgpu],
dependencies: drm_dep
)