1 /*
2 * Copyright 2022 Google
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include <fcntl.h>
7 #include <pthread.h>
8 #include <stdlib.h>
9 #include <sys/mman.h>
10 #include <unistd.h>
11 #include <xf86drm.h>
12
13 #include <cerrno>
14 #include <cstring>
15 #include <fstream>
16 #include <string>
17
18 #include "LinuxVirtGpu.h"
19 #include "drm-uapi/virtgpu_drm.h"
20 #include "util/log.h"
21 #include "virtgpu_gfxstream_protocol.h"
22
23 #define VIRTGPU_PARAM_CREATE_FENCE_PASSING 9 /* Fence passing */
24 #define VIRTGPU_PARAM_CREATE_GUEST_HANDLE 10 /* Host OS handle can be created from guest memory. */
25
26 #define PARAM(x) \
27 (struct VirtGpuParam) { x, #x, 0 }
28
align_up(uint32_t n,uint32_t a)29 static inline uint32_t align_up(uint32_t n, uint32_t a) { return ((n + a - 1) / a) * a; }
30
LinuxVirtGpuDevice(enum VirtGpuCapset capset,int32_t descriptor)31 LinuxVirtGpuDevice::LinuxVirtGpuDevice(enum VirtGpuCapset capset, int32_t descriptor)
32 : VirtGpuDevice(capset) {
33 struct VirtGpuParam params[] = {
34 PARAM(VIRTGPU_PARAM_3D_FEATURES), PARAM(VIRTGPU_PARAM_CAPSET_QUERY_FIX),
35 PARAM(VIRTGPU_PARAM_RESOURCE_BLOB), PARAM(VIRTGPU_PARAM_HOST_VISIBLE),
36 PARAM(VIRTGPU_PARAM_CROSS_DEVICE), PARAM(VIRTGPU_PARAM_CONTEXT_INIT),
37 PARAM(VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs), PARAM(VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME),
38 PARAM(VIRTGPU_PARAM_CREATE_FENCE_PASSING),
39 PARAM(VIRTGPU_PARAM_CREATE_GUEST_HANDLE),
40 };
41
42 int ret;
43 struct drm_virtgpu_get_caps get_caps = {0};
44 struct drm_virtgpu_context_init init = {0};
45 struct drm_virtgpu_context_set_param ctx_set_params[3] = {{0}};
46 const char* processName = nullptr;
47
48 memset(&mCaps, 0, sizeof(struct VirtGpuCaps));
49
50 #ifdef __ANDROID__
51 processName = getprogname();
52 #endif
53
54 if (descriptor < 0) {
55 mDeviceHandle = static_cast<int64_t>(drmOpenRender(128));
56 if (mDeviceHandle < 0) {
57 mesa_loge("Failed to open rendernode: %s", strerror(errno));
58 return;
59 }
60 } else {
61 mDeviceHandle = dup(descriptor);
62 if (mDeviceHandle < 0) {
63 mesa_loge("Failed to dup rendernode: %s", strerror(errno));
64 return;
65 }
66 }
67
68 for (uint32_t i = 0; i < kParamMax; i++) {
69 struct drm_virtgpu_getparam get_param = {0};
70 get_param.param = params[i].param;
71 get_param.value = (uint64_t)(uintptr_t)¶ms[i].value;
72
73 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_GETPARAM, &get_param);
74 if (ret) {
75 mesa_logi("virtgpu backend not enabling %s", params[i].name);
76 continue;
77 }
78
79 mCaps.params[i] = params[i].value;
80 }
81
82 get_caps.cap_set_id = static_cast<uint32_t>(capset);
83 switch (capset) {
84 case kCapsetGfxStreamVulkan:
85 get_caps.size = sizeof(struct vulkanCapset);
86 get_caps.addr = (unsigned long long)&mCaps.vulkanCapset;
87 break;
88 case kCapsetGfxStreamMagma:
89 get_caps.size = sizeof(struct magmaCapset);
90 get_caps.addr = (unsigned long long)&mCaps.magmaCapset;
91 break;
92 case kCapsetGfxStreamGles:
93 get_caps.size = sizeof(struct vulkanCapset);
94 get_caps.addr = (unsigned long long)&mCaps.glesCapset;
95 break;
96 case kCapsetGfxStreamComposer:
97 get_caps.size = sizeof(struct vulkanCapset);
98 get_caps.addr = (unsigned long long)&mCaps.composerCapset;
99 break;
100 default:
101 get_caps.size = 0;
102 }
103
104 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_GET_CAPS, &get_caps);
105 if (ret) {
106 // Don't fail get capabilities just yet, AEMU doesn't use this API
107 // yet (b/272121235);
108 mesa_loge("DRM_IOCTL_VIRTGPU_GET_CAPS failed with %s", strerror(errno));
109 }
110
111 // We always need an ASG blob in some cases, so always define blobAlignment
112 if (!mCaps.vulkanCapset.blobAlignment) {
113 mCaps.vulkanCapset.blobAlignment = 4096;
114 }
115
116 ctx_set_params[0].param = VIRTGPU_CONTEXT_PARAM_NUM_RINGS;
117 ctx_set_params[0].value = 2;
118 init.num_params = 1;
119
120 if (capset != kCapsetNone) {
121 ctx_set_params[init.num_params].param = VIRTGPU_CONTEXT_PARAM_CAPSET_ID;
122 ctx_set_params[init.num_params].value = static_cast<uint32_t>(capset);
123 init.num_params++;
124 }
125
126 if (mCaps.params[kParamExplicitDebugName] && processName) {
127 ctx_set_params[init.num_params].param = VIRTGPU_CONTEXT_PARAM_DEBUG_NAME;
128 ctx_set_params[init.num_params].value = reinterpret_cast<uint64_t>(processName);
129 init.num_params++;
130 }
131
132 init.ctx_set_params = (unsigned long long)&ctx_set_params[0];
133 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_CONTEXT_INIT, &init);
134 if (ret) {
135 mesa_loge("DRM_IOCTL_VIRTGPU_CONTEXT_INIT failed with %s, continuing without context...",
136 strerror(errno));
137 }
138 }
139
~LinuxVirtGpuDevice()140 LinuxVirtGpuDevice::~LinuxVirtGpuDevice() { close(mDeviceHandle); }
141
getCaps(void)142 struct VirtGpuCaps LinuxVirtGpuDevice::getCaps(void) { return mCaps; }
143
getDeviceHandle(void)144 int64_t LinuxVirtGpuDevice::getDeviceHandle(void) { return mDeviceHandle; }
145
createResource(uint32_t width,uint32_t height,uint32_t stride,uint32_t size,uint32_t virglFormat,uint32_t target,uint32_t bind)146 VirtGpuResourcePtr LinuxVirtGpuDevice::createResource(uint32_t width, uint32_t height,
147 uint32_t stride, uint32_t size,
148 uint32_t virglFormat, uint32_t target,
149 uint32_t bind) {
150 drm_virtgpu_resource_create create = {
151 .target = target,
152 .format = virglFormat,
153 .bind = bind,
154 .width = width,
155 .height = height,
156 .depth = 1U,
157 .array_size = 1U,
158 .last_level = 0,
159 .nr_samples = 0,
160 .size = size,
161 .stride = stride,
162 };
163
164 int ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create);
165 if (ret) {
166 mesa_loge("DRM_IOCTL_VIRTGPU_RESOURCE_CREATE failed with %s", strerror(errno));
167 return nullptr;
168 }
169
170 return std::make_shared<LinuxVirtGpuResource>(
171 mDeviceHandle, create.bo_handle, create.res_handle, static_cast<uint64_t>(create.size));
172 }
173
createBlob(const struct VirtGpuCreateBlob & blobCreate)174 VirtGpuResourcePtr LinuxVirtGpuDevice::createBlob(const struct VirtGpuCreateBlob& blobCreate) {
175 int ret;
176 struct drm_virtgpu_resource_create_blob create = {0};
177
178 create.size = blobCreate.size;
179 create.blob_mem = blobCreate.blobMem;
180 create.blob_flags = blobCreate.flags;
181 create.blob_id = blobCreate.blobId;
182 create.cmd = (uint64_t)(uintptr_t)blobCreate.blobCmd;
183 create.cmd_size = blobCreate.blobCmdSize;
184
185 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &create);
186 if (ret < 0) {
187 mesa_loge("DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s", strerror(errno));
188 return nullptr;
189 }
190
191 return std::make_shared<LinuxVirtGpuResource>(mDeviceHandle, create.bo_handle,
192 create.res_handle, blobCreate.size);
193 }
194
importBlob(const struct VirtGpuExternalHandle & handle)195 VirtGpuResourcePtr LinuxVirtGpuDevice::importBlob(const struct VirtGpuExternalHandle& handle) {
196 struct drm_virtgpu_resource_info info = {0};
197 uint32_t blobHandle;
198 int ret;
199
200 ret = drmPrimeFDToHandle(mDeviceHandle, handle.osHandle, &blobHandle);
201 close(handle.osHandle);
202 if (ret) {
203 mesa_loge("DRM_IOCTL_PRIME_FD_TO_HANDLE failed: %s", strerror(errno));
204 return nullptr;
205 }
206
207 info.bo_handle = blobHandle;
208 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &info);
209 if (ret) {
210 mesa_loge("DRM_IOCTL_VIRTGPU_RESOURCE_INFO failed: %s", strerror(errno));
211 return nullptr;
212 }
213
214 return std::make_shared<LinuxVirtGpuResource>(mDeviceHandle, blobHandle, info.res_handle,
215 static_cast<uint64_t>(info.size));
216 }
217
execBuffer(struct VirtGpuExecBuffer & execbuffer,const VirtGpuResource * blob)218 int LinuxVirtGpuDevice::execBuffer(struct VirtGpuExecBuffer& execbuffer,
219 const VirtGpuResource* blob) {
220 int ret;
221 struct drm_virtgpu_execbuffer exec = {0};
222 uint32_t blobHandle;
223
224 exec.flags = execbuffer.flags;
225 exec.size = execbuffer.command_size;
226 exec.ring_idx = execbuffer.ring_idx;
227 exec.command = (uint64_t)(uintptr_t)(execbuffer.command);
228 exec.fence_fd = -1;
229
230 if (blob) {
231 blobHandle = blob->getBlobHandle();
232 exec.bo_handles = (uint64_t)(uintptr_t)(&blobHandle);
233 exec.num_bo_handles = 1;
234 }
235
236 ret = drmIoctl(mDeviceHandle, DRM_IOCTL_VIRTGPU_EXECBUFFER, &exec);
237 if (ret) {
238 mesa_loge("DRM_IOCTL_VIRTGPU_EXECBUFFER failed: %s", strerror(errno));
239 return ret;
240 }
241
242 if (execbuffer.flags & kFenceOut) {
243 execbuffer.handle.osHandle = exec.fence_fd;
244 execbuffer.handle.type = kFenceHandleSyncFd;
245 }
246
247 return 0;
248 }
249
osCreateVirtGpuDevice(enum VirtGpuCapset capset,int32_t descriptor)250 VirtGpuDevice* osCreateVirtGpuDevice(enum VirtGpuCapset capset, int32_t descriptor) {
251 return new LinuxVirtGpuDevice(capset, descriptor);
252 }
253