• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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