1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "VirtioGpuPipeStream.h"
18 #include "virtgpu_drm.h"
19
20 #include <xf86drm.h>
21
22 #include <sys/types.h>
23 #include <sys/mman.h>
24
25 #include <errno.h>
26 #include <unistd.h>
27
28 // In a virtual machine, there should only be one GPU
29 #define RENDERNODE_MINOR 128
30
31 // Attributes use to allocate our response buffer
32 // Similar to virgl's fence objects
33 #define PIPE_BUFFER 0
34 #define VIRGL_FORMAT_R8_UNORM 64
35 #define VIRGL_BIND_CUSTOM (1 << 17)
36
37 static const size_t kTransferBufferSize = (1048576);
38
39 static const size_t kReadSize = 512 * 1024;
40 static const size_t kWriteOffset = kReadSize;
41
VirtioGpuPipeStream(size_t bufSize)42 VirtioGpuPipeStream::VirtioGpuPipeStream(size_t bufSize) :
43 IOStream(bufSize),
44 m_fd(-1),
45 m_virtio_rh(~0U),
46 m_virtio_bo(0),
47 m_virtio_mapped(nullptr),
48 m_bufsize(bufSize),
49 m_buf(nullptr),
50 m_read(0),
51 m_readLeft(0),
52 m_writtenPos(0),
53 m_fd_owned(true) { }
54
VirtioGpuPipeStream(size_t bufSize,int stream_handle)55 VirtioGpuPipeStream::VirtioGpuPipeStream(size_t bufSize, int stream_handle) :
56 IOStream(bufSize),
57 m_fd(stream_handle),
58 m_virtio_rh(~0U),
59 m_virtio_bo(0),
60 m_virtio_mapped(nullptr),
61 m_bufsize(bufSize),
62 m_buf(nullptr),
63 m_read(0),
64 m_readLeft(0),
65 m_writtenPos(0),
66 m_fd_owned(false) { }
67
~VirtioGpuPipeStream()68 VirtioGpuPipeStream::~VirtioGpuPipeStream()
69 {
70 if (m_virtio_mapped) {
71 munmap(m_virtio_mapped, kTransferBufferSize);
72 }
73
74 if (m_virtio_bo > 0U) {
75 drm_gem_close gem_close = {
76 .handle = m_virtio_bo,
77 };
78 drmIoctl(m_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
79 }
80
81 if (m_fd >= 0 && m_fd_owned) {
82 close(m_fd);
83 }
84
85 free(m_buf);
86 }
87
connect(const char * serviceName)88 int VirtioGpuPipeStream::connect(const char* serviceName)
89 {
90 if (m_fd < 0) {
91 m_fd = VirtioGpuPipeStream::openRendernode();
92 if (m_fd < 0) {
93 ERR("%s: failed with fd %d (%s)", __func__, m_fd, strerror(errno));
94 return -1;
95 }
96 }
97
98 if (!m_virtio_bo) {
99 drm_virtgpu_resource_create create = {
100 .target = PIPE_BUFFER,
101 .format = VIRGL_FORMAT_R8_UNORM,
102 .bind = VIRGL_BIND_CUSTOM,
103 .width = kTransferBufferSize,
104 .height = 1U,
105 .depth = 1U,
106 .array_size = 0U,
107 .size = kTransferBufferSize,
108 .stride = kTransferBufferSize,
109 };
110
111 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create);
112 if (ret) {
113 ERR("%s: failed with %d allocating command buffer (%s)",
114 __func__, ret, strerror(errno));
115 return -1;
116 }
117
118 m_virtio_bo = create.bo_handle;
119 if (!m_virtio_bo) {
120 ERR("%s: no handle when allocating command buffer",
121 __func__);
122 return -1;
123 }
124
125 m_virtio_rh = create.res_handle;
126
127 if (create.size != kTransferBufferSize) {
128 ERR("%s: command buffer wrongly sized, create.size=%zu "
129 "!= %zu", __func__,
130 static_cast<size_t>(create.size),
131 static_cast<size_t>(kTransferBufferSize));
132 abort();
133 }
134 }
135
136 if (!m_virtio_mapped) {
137 drm_virtgpu_map map;
138 memset(&map, 0, sizeof(map));
139 map.handle = m_virtio_bo;
140
141 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_MAP, &map);
142 if (ret) {
143 ERR("%s: failed with %d mapping command response buffer (%s)",
144 __func__, ret, strerror(errno));
145 return -1;
146 }
147
148 m_virtio_mapped = static_cast<unsigned char*>(
149 mmap64(nullptr, kTransferBufferSize, PROT_WRITE,
150 MAP_SHARED, m_fd, map.offset));
151
152 if (m_virtio_mapped == MAP_FAILED) {
153 ERR("%s: failed with %d mmap'ing command response buffer (%s)",
154 __func__, ret, strerror(errno));
155 return -1;
156 }
157 }
158
159 wait();
160
161 if (serviceName) {
162 writeFully(serviceName, strlen(serviceName) + 1);
163 } else {
164 static const char kPipeString[] = "pipe:opengles";
165 std::string pipeStr(kPipeString);
166 writeFully(kPipeString, sizeof(kPipeString));
167 }
168 return 0;
169 }
170
openRendernode()171 int VirtioGpuPipeStream::openRendernode() {
172 int fd = drmOpenRender(RENDERNODE_MINOR);
173 if (fd < 0) {
174 ERR("%s: failed with fd %d (%s)", __func__, fd, strerror(errno));
175 return -1;
176 }
177 return fd;
178 }
179
initProcessPipe()180 uint64_t VirtioGpuPipeStream::initProcessPipe() {
181 connect("pipe:GLProcessPipe");
182 int32_t confirmInt = 100;
183 writeFully(&confirmInt, sizeof(confirmInt));
184 uint64_t res;
185 readFully(&res, sizeof(res));
186 return res;
187 }
188
allocBuffer(size_t minSize)189 void *VirtioGpuPipeStream::allocBuffer(size_t minSize) {
190 size_t allocSize = (m_bufsize < minSize ? minSize : m_bufsize);
191 if (!m_buf) {
192 m_buf = (unsigned char *)malloc(allocSize);
193 }
194 else if (m_bufsize < allocSize) {
195 unsigned char *p = (unsigned char *)realloc(m_buf, allocSize);
196 if (p != NULL) {
197 m_buf = p;
198 m_bufsize = allocSize;
199 } else {
200 ERR("realloc (%zu) failed\n", allocSize);
201 free(m_buf);
202 m_buf = NULL;
203 m_bufsize = 0;
204 }
205 }
206
207 return m_buf;
208 }
209
commitBuffer(size_t size)210 int VirtioGpuPipeStream::commitBuffer(size_t size) {
211 if (size == 0) return 0;
212 return writeFully(m_buf, size);
213 }
214
writeFully(const void * buf,size_t len)215 int VirtioGpuPipeStream::writeFully(const void *buf, size_t len)
216 {
217 //DBG(">> VirtioGpuPipeStream::writeFully %d\n", len);
218 if (!valid()) return -1;
219 if (!buf) {
220 if (len>0) {
221 // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
222 // in a corrupted state, which is lethal for the emulator.
223 ERR("VirtioGpuPipeStream::writeFully failed, buf=NULL, len %zu,"
224 " lethal error, exiting", len);
225 abort();
226 }
227 return 0;
228 }
229
230 size_t res = len;
231 int retval = 0;
232
233 while (res > 0) {
234 ssize_t stat = transferToHost((const char *)(buf) + (len - res), res);
235 if (stat > 0) {
236 res -= stat;
237 continue;
238 }
239 if (stat == 0) { /* EOF */
240 ERR("VirtioGpuPipeStream::writeFully failed: premature EOF\n");
241 retval = -1;
242 break;
243 }
244 if (errno == EAGAIN) {
245 continue;
246 }
247 retval = stat;
248 ERR("VirtioGpuPipeStream::writeFully failed: %s, lethal error, exiting.\n",
249 strerror(errno));
250 abort();
251 }
252 //DBG("<< VirtioGpuPipeStream::writeFully %d\n", len );
253 return retval;
254 }
255
readFully(void * buf,size_t len)256 const unsigned char *VirtioGpuPipeStream::readFully(void *buf, size_t len)
257 {
258 flush();
259
260 if (!valid()) return NULL;
261 if (!buf) {
262 if (len > 0) {
263 // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
264 // in a corrupted state, which is lethal for the emulator.
265 ERR("VirtioGpuPipeStream::readFully failed, buf=NULL, len %zu, lethal"
266 " error, exiting.", len);
267 abort();
268 }
269 }
270
271 size_t res = len;
272 while (res > 0) {
273 ssize_t stat = transferFromHost((char *)(buf) + len - res, res);
274 if (stat == 0) {
275 // client shutdown;
276 return NULL;
277 } else if (stat < 0) {
278 if (errno == EAGAIN) {
279 continue;
280 } else {
281 ERR("VirtioGpuPipeStream::readFully failed (buf %p, len %zu"
282 ", res %zu): %s, lethal error, exiting.", buf, len, res,
283 strerror(errno));
284 abort();
285 }
286 } else {
287 res -= stat;
288 }
289 }
290 //DBG("<< VirtioGpuPipeStream::readFully %d\n", len);
291 return (const unsigned char *)buf;
292 }
293
commitBufferAndReadFully(size_t writeSize,void * userReadBufPtr,size_t totalReadSize)294 const unsigned char *VirtioGpuPipeStream::commitBufferAndReadFully(
295 size_t writeSize, void *userReadBufPtr, size_t totalReadSize)
296 {
297 return commitBuffer(writeSize) ? nullptr : readFully(userReadBufPtr, totalReadSize);
298 }
299
read(void * buf,size_t * inout_len)300 const unsigned char *VirtioGpuPipeStream::read( void *buf, size_t *inout_len)
301 {
302 //DBG(">> VirtioGpuPipeStream::read %d\n", *inout_len);
303 if (!valid()) return NULL;
304 if (!buf) {
305 ERR("VirtioGpuPipeStream::read failed, buf=NULL");
306 return NULL; // do not allow NULL buf in that implementation
307 }
308
309 int n = recv(buf, *inout_len);
310
311 if (n > 0) {
312 *inout_len = n;
313 return (const unsigned char *)buf;
314 }
315
316 //DBG("<< VirtioGpuPipeStream::read %d\n", *inout_len);
317 return NULL;
318 }
319
recv(void * buf,size_t len)320 int VirtioGpuPipeStream::recv(void *buf, size_t len)
321 {
322 if (!valid()) return int(ERR_INVALID_SOCKET);
323 char* p = (char *)buf;
324 int ret = 0;
325 while(len > 0) {
326 int res = transferFromHost(p, len);
327 if (res > 0) {
328 p += res;
329 ret += res;
330 len -= res;
331 continue;
332 }
333 if (res == 0) { /* EOF */
334 break;
335 }
336 if (errno != EAGAIN) {
337 continue;
338 }
339
340 /* A real error */
341 if (ret == 0)
342 ret = -1;
343 break;
344 }
345 return ret;
346 }
347
wait()348 void VirtioGpuPipeStream::wait() {
349 struct drm_virtgpu_3d_wait waitcmd;
350 memset(&waitcmd, 0, sizeof(waitcmd));
351 waitcmd.handle = m_virtio_bo;
352 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_WAIT, &waitcmd);
353 if (ret) {
354 ERR("VirtioGpuPipeStream: DRM_IOCTL_VIRTGPU_WAIT failed with %d (%s)\n", errno, strerror(errno));
355 }
356 m_writtenPos = 0;
357 }
358
transferToHost(const void * buffer,size_t len)359 ssize_t VirtioGpuPipeStream::transferToHost(const void* buffer, size_t len) {
360 size_t todo = len;
361 size_t done = 0;
362 int ret = EAGAIN;
363 struct drm_virtgpu_3d_transfer_to_host xfer;
364
365 unsigned char* virtioPtr = m_virtio_mapped;
366
367 const unsigned char* readPtr = reinterpret_cast<const unsigned char*>(buffer);
368
369 while (done < len) {
370 size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
371
372 if (toXfer > (kTransferBufferSize - m_writtenPos)) {
373 wait();
374 }
375
376 memcpy(virtioPtr + m_writtenPos, readPtr, toXfer);
377
378 memset(&xfer, 0, sizeof(xfer));
379 xfer.bo_handle = m_virtio_bo;
380 xfer.box.x = m_writtenPos;
381 xfer.box.y = 0;
382 xfer.box.w = toXfer;
383 xfer.box.h = 1;
384 xfer.box.d = 1;
385
386 ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST, &xfer);
387
388 if (ret) {
389 ERR("VirtioGpuPipeStream: failed with errno %d (%s)\n", errno, strerror(errno));
390 return (ssize_t)ret;
391 }
392
393 done += toXfer;
394 readPtr += toXfer;
395 todo -= toXfer;
396 m_writtenPos += toXfer;
397 }
398
399 return len;
400 }
401
transferFromHost(void * buffer,size_t len)402 ssize_t VirtioGpuPipeStream::transferFromHost(void* buffer, size_t len) {
403 size_t todo = len;
404 size_t done = 0;
405 int ret = EAGAIN;
406 struct drm_virtgpu_3d_transfer_from_host xfer;
407
408 const unsigned char* virtioPtr = m_virtio_mapped;
409 unsigned char* readPtr = reinterpret_cast<unsigned char*>(buffer);
410
411 if (m_writtenPos) {
412 wait();
413 }
414
415 while (done < len) {
416 size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
417
418 memset(&xfer, 0, sizeof(xfer));
419 xfer.bo_handle = m_virtio_bo;
420 xfer.box.x = 0;
421 xfer.box.y = 0;
422 xfer.box.w = toXfer;
423 xfer.box.h = 1;
424 xfer.box.d = 1;
425
426 ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST, &xfer);
427
428 if (ret) {
429 ERR("VirtioGpuPipeStream: failed with errno %d (%s)\n", errno, strerror(errno));
430 return (ssize_t)ret;
431 }
432
433 wait();
434
435 memcpy(readPtr, virtioPtr, toXfer);
436
437 done += toXfer;
438 readPtr += toXfer;
439 todo -= toXfer;
440 }
441
442 return len;
443 }
444