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