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
19 #include <drm/virtgpu_drm.h>
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
~VirtioGpuPipeStream()54 VirtioGpuPipeStream::~VirtioGpuPipeStream()
55 {
56 if (m_virtio_mapped) {
57 munmap(m_virtio_mapped, kTransferBufferSize);
58 }
59
60 if (m_virtio_bo > 0U) {
61 drm_gem_close gem_close = {
62 .handle = m_virtio_bo,
63 };
64 drmIoctl(m_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
65 }
66
67 if (m_fd >= 0) {
68 close(m_fd);
69 }
70
71 free(m_buf);
72 }
73
connect(const char * serviceName)74 int VirtioGpuPipeStream::connect(const char* serviceName)
75 {
76 if (m_fd < 0) {
77 m_fd = VirtioGpuPipeStream::openRendernode();
78 if (m_fd < 0) {
79 ERR("%s: failed with fd %d (%s)", __func__, m_fd, strerror(errno));
80 return -1;
81 }
82 }
83
84 if (!m_virtio_bo) {
85 drm_virtgpu_resource_create create = {
86 .target = PIPE_BUFFER,
87 .format = VIRGL_FORMAT_R8_UNORM,
88 .bind = VIRGL_BIND_CUSTOM,
89 .width = kTransferBufferSize,
90 .height = 1U,
91 .depth = 1U,
92 .array_size = 0U,
93 .size = kTransferBufferSize,
94 .stride = kTransferBufferSize,
95 };
96
97 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE, &create);
98 if (ret) {
99 ERR("%s: failed with %d allocating command buffer (%s)",
100 __func__, ret, strerror(errno));
101 return -1;
102 }
103
104 m_virtio_bo = create.bo_handle;
105 if (!m_virtio_bo) {
106 ERR("%s: no handle when allocating command buffer",
107 __func__);
108 return -1;
109 }
110
111 m_virtio_rh = create.res_handle;
112
113 if (create.size != kTransferBufferSize) {
114 ERR("%s: command buffer wrongly sized, create.size=%zu "
115 "!= %zu", __func__,
116 static_cast<size_t>(create.size),
117 static_cast<size_t>(kTransferBufferSize));
118 abort();
119 }
120 }
121
122 if (!m_virtio_mapped) {
123 drm_virtgpu_map map = {
124 .handle = m_virtio_bo,
125 };
126
127 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_MAP, &map);
128 if (ret) {
129 ERR("%s: failed with %d mapping command response buffer (%s)",
130 __func__, ret, strerror(errno));
131 return -1;
132 }
133
134 m_virtio_mapped = static_cast<unsigned char*>(
135 mmap64(nullptr, kTransferBufferSize, PROT_WRITE,
136 MAP_SHARED, m_fd, map.offset));
137
138 if (m_virtio_mapped == MAP_FAILED) {
139 ERR("%s: failed with %d mmap'ing command response buffer (%s)",
140 __func__, ret, strerror(errno));
141 return -1;
142 }
143 }
144
145 wait();
146
147 if (serviceName) {
148 writeFully(serviceName, strlen(serviceName) + 1);
149 } else {
150 static const char kPipeString[] = "pipe:opengles";
151 std::string pipeStr(kPipeString);
152 writeFully(kPipeString, sizeof(kPipeString));
153 }
154 return 0;
155 }
156
openRendernode()157 int VirtioGpuPipeStream::openRendernode() {
158 int fd = drmOpenRender(RENDERNODE_MINOR);
159 if (fd < 0) {
160 ERR("%s: failed with fd %d (%s)", __func__, fd, strerror(errno));
161 return -1;
162 }
163 return fd;
164 }
165
initProcessPipe()166 uint64_t VirtioGpuPipeStream::initProcessPipe() {
167 connect("pipe:GLProcessPipe");
168 int32_t confirmInt = 100;
169 writeFully(&confirmInt, sizeof(confirmInt));
170 uint64_t res;
171 readFully(&res, sizeof(res));
172 return res;
173 }
174
allocBuffer(size_t minSize)175 void *VirtioGpuPipeStream::allocBuffer(size_t minSize) {
176 size_t allocSize = (m_bufsize < minSize ? minSize : m_bufsize);
177 if (!m_buf) {
178 m_buf = (unsigned char *)malloc(allocSize);
179 }
180 else if (m_bufsize < allocSize) {
181 unsigned char *p = (unsigned char *)realloc(m_buf, allocSize);
182 if (p != NULL) {
183 m_buf = p;
184 m_bufsize = allocSize;
185 } else {
186 ERR("realloc (%zu) failed\n", allocSize);
187 free(m_buf);
188 m_buf = NULL;
189 m_bufsize = 0;
190 }
191 }
192
193 return m_buf;
194 }
195
commitBuffer(size_t size)196 int VirtioGpuPipeStream::commitBuffer(size_t size) {
197 if (size == 0) return 0;
198 return writeFully(m_buf, size);
199 }
200
writeFully(const void * buf,size_t len)201 int VirtioGpuPipeStream::writeFully(const void *buf, size_t len)
202 {
203 //DBG(">> VirtioGpuPipeStream::writeFully %d\n", len);
204 if (!valid()) return -1;
205 if (!buf) {
206 if (len>0) {
207 // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
208 // in a corrupted state, which is lethal for the emulator.
209 ERR("VirtioGpuPipeStream::writeFully failed, buf=NULL, len %zu,"
210 " lethal error, exiting", len);
211 abort();
212 }
213 return 0;
214 }
215
216 size_t res = len;
217 int retval = 0;
218
219 while (res > 0) {
220 ssize_t stat = transferToHost((const char *)(buf) + (len - res), res);
221 if (stat > 0) {
222 res -= stat;
223 continue;
224 }
225 if (stat == 0) { /* EOF */
226 ERR("VirtioGpuPipeStream::writeFully failed: premature EOF\n");
227 retval = -1;
228 break;
229 }
230 if (errno == EAGAIN) {
231 continue;
232 }
233 retval = stat;
234 ERR("VirtioGpuPipeStream::writeFully failed: %s, lethal error, exiting.\n",
235 strerror(errno));
236 abort();
237 }
238 //DBG("<< VirtioGpuPipeStream::writeFully %d\n", len );
239 return retval;
240 }
241
readFully(void * buf,size_t len)242 const unsigned char *VirtioGpuPipeStream::readFully(void *buf, size_t len)
243 {
244 flush();
245
246 if (!valid()) return NULL;
247 if (!buf) {
248 if (len > 0) {
249 // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
250 // in a corrupted state, which is lethal for the emulator.
251 ERR("VirtioGpuPipeStream::readFully failed, buf=NULL, len %zu, lethal"
252 " error, exiting.", len);
253 abort();
254 }
255 }
256
257 size_t res = len;
258 while (res > 0) {
259 ssize_t stat = transferFromHost((char *)(buf) + len - res, res);
260 if (stat == 0) {
261 // client shutdown;
262 return NULL;
263 } else if (stat < 0) {
264 if (errno == EAGAIN) {
265 continue;
266 } else {
267 ERR("VirtioGpuPipeStream::readFully failed (buf %p, len %zu"
268 ", res %zu): %s, lethal error, exiting.", buf, len, res,
269 strerror(errno));
270 abort();
271 }
272 } else {
273 res -= stat;
274 }
275 }
276 //DBG("<< VirtioGpuPipeStream::readFully %d\n", len);
277 return (const unsigned char *)buf;
278 }
279
commitBufferAndReadFully(size_t writeSize,void * userReadBufPtr,size_t totalReadSize)280 const unsigned char *VirtioGpuPipeStream::commitBufferAndReadFully(
281 size_t writeSize, void *userReadBufPtr, size_t totalReadSize)
282 {
283 return commitBuffer(writeSize) ? nullptr : readFully(userReadBufPtr, totalReadSize);
284 }
285
read(void * buf,size_t * inout_len)286 const unsigned char *VirtioGpuPipeStream::read( void *buf, size_t *inout_len)
287 {
288 //DBG(">> VirtioGpuPipeStream::read %d\n", *inout_len);
289 if (!valid()) return NULL;
290 if (!buf) {
291 ERR("VirtioGpuPipeStream::read failed, buf=NULL");
292 return NULL; // do not allow NULL buf in that implementation
293 }
294
295 int n = recv(buf, *inout_len);
296
297 if (n > 0) {
298 *inout_len = n;
299 return (const unsigned char *)buf;
300 }
301
302 //DBG("<< VirtioGpuPipeStream::read %d\n", *inout_len);
303 return NULL;
304 }
305
recv(void * buf,size_t len)306 int VirtioGpuPipeStream::recv(void *buf, size_t len)
307 {
308 if (!valid()) return int(ERR_INVALID_SOCKET);
309 char* p = (char *)buf;
310 int ret = 0;
311 while(len > 0) {
312 int res = transferFromHost(p, len);
313 if (res > 0) {
314 p += res;
315 ret += res;
316 len -= res;
317 continue;
318 }
319 if (res == 0) { /* EOF */
320 break;
321 }
322 if (errno != EAGAIN) {
323 continue;
324 }
325
326 /* A real error */
327 if (ret == 0)
328 ret = -1;
329 break;
330 }
331 return ret;
332 }
333
wait()334 void VirtioGpuPipeStream::wait() {
335 struct drm_virtgpu_3d_wait waitcmd;
336 memset(&waitcmd, 0, sizeof(waitcmd));
337 waitcmd.handle = m_virtio_bo;
338 int ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_WAIT, &waitcmd);
339 if (ret) {
340 ERR("VirtioGpuPipeStream: DRM_IOCTL_VIRTGPU_WAIT failed with %d (%s)\n", errno, strerror(errno));
341 }
342 m_writtenPos = 0;
343 }
344
transferToHost(const void * buffer,size_t len)345 ssize_t VirtioGpuPipeStream::transferToHost(const void* buffer, size_t len) {
346 size_t todo = len;
347 size_t done = 0;
348 int ret = EAGAIN;
349 struct drm_virtgpu_3d_transfer_to_host xfer;
350
351 unsigned char* virtioPtr = m_virtio_mapped;
352
353 const unsigned char* readPtr = reinterpret_cast<const unsigned char*>(buffer);
354
355 while (done < len) {
356 size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
357
358 if (toXfer > (kTransferBufferSize - m_writtenPos)) {
359 wait();
360 }
361
362 memcpy(virtioPtr + m_writtenPos, readPtr, toXfer);
363
364 memset(&xfer, 0, sizeof(xfer));
365 xfer.bo_handle = m_virtio_bo;
366 xfer.box.x = m_writtenPos;
367 xfer.box.y = 0;
368 xfer.box.w = toXfer;
369 xfer.box.h = 1;
370 xfer.box.d = 1;
371
372 ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST, &xfer);
373
374 if (ret) {
375 ERR("VirtioGpuPipeStream: failed with errno %d (%s)\n", errno, strerror(errno));
376 return (ssize_t)ret;
377 }
378
379 done += toXfer;
380 readPtr += toXfer;
381 todo -= toXfer;
382 m_writtenPos += toXfer;
383 }
384
385 return len;
386 }
387
transferFromHost(void * buffer,size_t len)388 ssize_t VirtioGpuPipeStream::transferFromHost(void* buffer, size_t len) {
389 size_t todo = len;
390 size_t done = 0;
391 int ret = EAGAIN;
392 struct drm_virtgpu_3d_transfer_from_host xfer;
393
394 const unsigned char* virtioPtr = m_virtio_mapped;
395 unsigned char* readPtr = reinterpret_cast<unsigned char*>(buffer);
396
397 if (m_writtenPos) {
398 wait();
399 }
400
401 while (done < len) {
402 size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
403
404 memset(&xfer, 0, sizeof(xfer));
405 xfer.bo_handle = m_virtio_bo;
406 xfer.box.x = 0;
407 xfer.box.y = 0;
408 xfer.box.w = toXfer;
409 xfer.box.h = 1;
410 xfer.box.d = 1;
411
412 ret = drmIoctl(m_fd, DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST, &xfer);
413
414 if (ret) {
415 ERR("VirtioGpuPipeStream: failed with errno %d (%s)\n", errno, strerror(errno));
416 return (ssize_t)ret;
417 }
418
419 wait();
420
421 memcpy(readPtr, virtioPtr, toXfer);
422
423 done += toXfer;
424 readPtr += toXfer;
425 todo -= toXfer;
426 }
427
428 return len;
429 }
430