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:

committed by
Marge Bot

parent
e7116abf90
commit
de7095ba5b
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "GfxStreamConnection.h"
|
||||
|
||||
GfxStreamConnection::GfxStreamConnection() {}
|
||||
|
||||
GfxStreamConnection::~GfxStreamConnection() {}
|
17
src/gfxstream/guest/connection-manager/GfxStreamConnection.h
Normal file
17
src/gfxstream/guest/connection-manager/GfxStreamConnection.h
Normal 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
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
263
src/gfxstream/guest/connection-manager/QemuPipeStream.cpp
Normal file
263
src/gfxstream/guest/connection-manager/QemuPipeStream.cpp
Normal 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;
|
||||
}
|
62
src/gfxstream/guest/connection-manager/QemuPipeStream.h
Normal file
62
src/gfxstream/guest/connection-manager/QemuPipeStream.h
Normal 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
|
341
src/gfxstream/guest/connection-manager/VirtioGpuPipeStream.cpp
Normal file
341
src/gfxstream/guest/connection-manager/VirtioGpuPipeStream.cpp
Normal 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;
|
||||
}
|
68
src/gfxstream/guest/connection-manager/VirtioGpuPipeStream.h
Normal file
68
src/gfxstream/guest/connection-manager/VirtioGpuPipeStream.h
Normal 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;
|
||||
};
|
24
src/gfxstream/guest/connection-manager/meson.build
Normal file
24
src/gfxstream/guest/connection-manager/meson.build
Normal 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
|
||||
)
|
Reference in New Issue
Block a user