blob: e7299e6292514c79cff94660d2032ff32a4b518e [file] [log] [blame]
/*
* Copyright 2022 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 <cutils/log.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <string>
#include "LinuxVirtGpu.h"
#include "virtgpu_drm.h"
#include "virtgpu_gfxstream_protocol.h"
#define PARAM(x) \
(struct VirtGpuParam) { x, #x, 0 }
static inline uint32_t align_up(uint32_t n, uint32_t a) { return ((n + a - 1) / a) * a; }
LinuxVirtGpuDevice::LinuxVirtGpuDevice(enum VirtGpuCapset capset, int fd) : VirtGpuDevice(capset) {
struct VirtGpuParam params[] = {
PARAM(VIRTGPU_PARAM_3D_FEATURES), PARAM(VIRTGPU_PARAM_CAPSET_QUERY_FIX),
PARAM(VIRTGPU_PARAM_RESOURCE_BLOB), PARAM(VIRTGPU_PARAM_HOST_VISIBLE),
PARAM(VIRTGPU_PARAM_CROSS_DEVICE), PARAM(VIRTGPU_PARAM_CONTEXT_INIT),
PARAM(VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs), PARAM(VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME),
PARAM(VIRTGPU_PARAM_CREATE_GUEST_HANDLE),
};
int ret;
struct drm_virtgpu_get_caps get_caps = {0};
struct drm_virtgpu_context_init init = {0};
struct drm_virtgpu_context_set_param ctx_set_params[3] = {{0}};
const char* processName = nullptr;
memset(&mCaps, 0, sizeof(struct VirtGpuCaps));
#ifdef __ANDROID__
processName = getprogname();
#endif
if (fd < 0) {
mDeviceHandle = static_cast<int64_t>(drmOpenRender(128));
if (mDeviceHandle < 0) {
ALOGE("Failed to open rendernode: %s", strerror(errno));
return;
}
} else {
mDeviceHandle = dup(fd);
if (mDeviceHandle < 0) {
ALOGE("Failed to dup rendernode: %s", strerror(errno));
return;
}
}
for (uint32_t i = 0; i < kParamMax; i++) {
struct drm_virtgpu_getparam get_param = {0};
get_param.param = params[i].param;
get_param.value = (uint64_t)(uintptr_t)&params[i].value;
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_GETPARAM, &get_param);
if (ret) {
ALOGV("virtgpu backend not enabling %s", params[i].name);
continue;
}
mCaps.params[i] = params[i].value;
}
get_caps.cap_set_id = static_cast<uint32_t>(capset);
switch (capset) {
case kCapsetGfxStreamVulkan:
get_caps.size = sizeof(struct vulkanCapset);
get_caps.addr = (unsigned long long)&mCaps.vulkanCapset;
break;
case kCapsetGfxStreamMagma:
get_caps.size = sizeof(struct magmaCapset);
get_caps.addr = (unsigned long long)&mCaps.magmaCapset;
break;
case kCapsetGfxStreamGles:
get_caps.size = sizeof(struct vulkanCapset);
get_caps.addr = (unsigned long long)&mCaps.glesCapset;
break;
case kCapsetGfxStreamComposer:
get_caps.size = sizeof(struct vulkanCapset);
get_caps.addr = (unsigned long long)&mCaps.composerCapset;
break;
default:
get_caps.size = 0;
}
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_GET_CAPS, &get_caps);
if (ret) {
// Don't fail get capabilities just yet, AEMU doesn't use this API
// yet (b/272121235);
ALOGE("DRM_IOCTL_VIRTGPU_GET_CAPS failed with %s", strerror(errno));
}
// We always need an ASG blob in some cases, so always define blobAlignment
if (!mCaps.vulkanCapset.blobAlignment) {
mCaps.vulkanCapset.blobAlignment = 4096;
}
ctx_set_params[0].param = VIRTGPU_CONTEXT_PARAM_NUM_RINGS;
ctx_set_params[0].value = 2;
init.num_params = 1;
if (capset != kCapsetNone) {
ctx_set_params[init.num_params].param = VIRTGPU_CONTEXT_PARAM_CAPSET_ID;
ctx_set_params[init.num_params].value = static_cast<uint32_t>(capset);
init.num_params++;
}
if (mCaps.params[kParamExplicitDebugName] && processName) {
ctx_set_params[init.num_params].param = VIRTGPU_CONTEXT_PARAM_DEBUG_NAME;
ctx_set_params[init.num_params].value = reinterpret_cast<uint64_t>(processName);
init.num_params++;
}
init.ctx_set_params = (unsigned long long)&ctx_set_params[0];
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_CONTEXT_INIT, &init);
if (ret) {
ALOGE("DRM_IOCTL_VIRTGPU_CONTEXT_INIT failed with %s, continuing without context...",
strerror(errno));
}
}
LinuxVirtGpuDevice::~LinuxVirtGpuDevice() { close(mDeviceHandle); }
struct VirtGpuCaps LinuxVirtGpuDevice::getCaps(void) { return mCaps; }
int64_t LinuxVirtGpuDevice::getDeviceHandle(void) { return mDeviceHandle; }
VirtGpuResourcePtr LinuxVirtGpuDevice::createResource(uint32_t width, uint32_t height,
uint32_t virglFormat, uint32_t target,
uint32_t bind, uint32_t bpp) {
drm_virtgpu_resource_create create = {
.target = target,
.format = virglFormat,
.bind = bind,
.width = width,
.height = height,
.depth = 1U,
.array_size = 1U,
.last_level = 0,
.nr_samples = 0,
.size = width * height * bpp,
.stride = width * bpp,
};
int ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create);
if (ret) {
ALOGE("DRM_IOCTL_VIRTGPU_RESOURCE_CREATE failed with %s", strerror(errno));
return nullptr;
}
return std::make_shared<LinuxVirtGpuResource>(
mDeviceHandle, create.bo_handle, create.res_handle, static_cast<uint64_t>(create.size));
}
VirtGpuResourcePtr LinuxVirtGpuDevice::createBlob(const struct VirtGpuCreateBlob& blobCreate) {
int ret;
struct drm_virtgpu_resource_create_blob create = {0};
create.size = blobCreate.size;
create.blob_mem = blobCreate.blobMem;
create.blob_flags = blobCreate.flags;
create.blob_id = blobCreate.blobId;
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &create);
if (ret < 0) {
ALOGE("DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s", strerror(errno));
return nullptr;
}
return std::make_shared<LinuxVirtGpuResource>(mDeviceHandle, create.bo_handle,
create.res_handle, blobCreate.size);
}
VirtGpuResourcePtr LinuxVirtGpuDevice::importBlob(const struct VirtGpuExternalHandle& handle) {
struct drm_virtgpu_resource_info info = {0};
uint32_t blobHandle;
int ret;
ret = drmPrimeFDToHandle(mDeviceHandle, handle.osHandle, &blobHandle);
close(handle.osHandle);
if (ret) {
ALOGE("DRM_IOCTL_PRIME_FD_TO_HANDLE failed: %s", strerror(errno));
return nullptr;
}
info.bo_handle = blobHandle;
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &info);
if (ret) {
ALOGE("DRM_IOCTL_VIRTGPU_RESOURCE_INFO failed: %s", strerror(errno));
return nullptr;
}
return std::make_shared<LinuxVirtGpuResource>(mDeviceHandle, blobHandle, info.res_handle,
static_cast<uint64_t>(info.size));
}
int LinuxVirtGpuDevice::execBuffer(struct VirtGpuExecBuffer& execbuffer,
const VirtGpuResource* blob) {
int ret;
struct drm_virtgpu_execbuffer exec = {0};
uint32_t blobHandle;
exec.flags = execbuffer.flags;
exec.size = execbuffer.command_size;
exec.ring_idx = execbuffer.ring_idx;
exec.command = (uint64_t)(uintptr_t)(execbuffer.command);
exec.fence_fd = -1;
if (blob) {
blobHandle = blob->getBlobHandle();
exec.bo_handles = (uint64_t)(uintptr_t)(&blobHandle);
exec.num_bo_handles = 1;
}
ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_EXECBUFFER, &exec);
if (ret) {
ALOGE("DRM_IOCTL_VIRTGPU_EXECBUFFER failed: %s", strerror(errno));
return ret;
}
if (execbuffer.flags & kFenceOut) {
execbuffer.handle.osHandle = exec.fence_fd;
execbuffer.handle.type = kFenceHandleSyncFd;
}
return 0;
}
VirtGpuDevice* createPlatformVirtGpuDevice(enum VirtGpuCapset capset, int fd) {
return new LinuxVirtGpuDevice(capset, fd);
}