• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google LLC
3  * SPDX-License-Identifier: MIT
4  */
5 
6 #include "VirtioGpuPipeStream.h"
7 
8 #include <errno.h>
9 
10 #include "util/detect_os.h"
11 #if DETECT_OS_LINUX
12 #include <sys/mman.h>
13 #include <unistd.h>
14 #endif
15 #include <sys/types.h>
16 
17 #include <cstring>
18 #include <string>
19 
20 #include "VirtGpu.h"
21 #include "util/log.h"
22 
23 static const size_t kTransferBufferSize = (1048576);
24 
25 static const size_t kReadSize = 512 * 1024;
26 static const size_t kWriteOffset = kReadSize;
27 
VirtioGpuPipeStream(size_t bufSize,int32_t descriptor)28 VirtioGpuPipeStream::VirtioGpuPipeStream(size_t bufSize, int32_t descriptor)
29     : IOStream(bufSize),
30       m_fd(descriptor),
31       m_virtio_mapped(nullptr),
32       m_bufsize(bufSize),
33       m_buf(nullptr),
34       m_writtenPos(0) {}
35 
~VirtioGpuPipeStream()36 VirtioGpuPipeStream::~VirtioGpuPipeStream() { free(m_buf); }
37 
valid()38 bool VirtioGpuPipeStream::valid() { return m_device != nullptr; }
39 
getRendernodeFd()40 int VirtioGpuPipeStream::getRendernodeFd() {
41     if (m_device == nullptr) {
42         return -1;
43     }
44     return m_device->getDeviceHandle();
45 }
46 
connect(const char * serviceName)47 int VirtioGpuPipeStream::connect(const char* serviceName) {
48     if (!m_device) {
49         m_device.reset(createPlatformVirtGpuDevice(kCapsetNone, m_fd));
50         if (!m_device) {
51             mesa_loge("Failed to create VirtioGpuPipeStream VirtGpuDevice.");
52             return -1;
53         }
54 
55         m_resource = m_device->createResource(/*width=*/kTransferBufferSize,
56                                               /*height=*/1,
57                                               /*stride=*/kTransferBufferSize,
58                                               /*size=*/kTransferBufferSize, VIRGL_FORMAT_R8_UNORM,
59                                               PIPE_BUFFER, VIRGL_BIND_CUSTOM);
60         if (!m_resource) {
61             mesa_loge("Failed to create VirtioGpuPipeStream resource.");
62             return -1;
63         }
64 
65         m_resourceMapping = m_resource->createMapping();
66         if (!m_resourceMapping) {
67             mesa_loge("Failed to create VirtioGpuPipeStream resource mapping.");
68             return -1;
69         }
70 
71         m_virtio_mapped = m_resourceMapping->asRawPtr();
72         if (!m_virtio_mapped) {
73             mesa_loge("Failed to create VirtioGpuPipeStream resource mapping ptr.");
74             return -1;
75         }
76     }
77 
78     wait();
79 
80     if (serviceName) {
81         writeFully(serviceName, strlen(serviceName) + 1);
82     } else {
83         static const char kPipeString[] = "pipe:opengles";
84         std::string pipeStr(kPipeString);
85         writeFully(kPipeString, sizeof(kPipeString));
86     }
87 
88     return 0;
89 }
90 
processPipeInit()91 uint64_t VirtioGpuPipeStream::processPipeInit() {
92     connect("pipe:GLProcessPipe");
93     int32_t confirmInt = 100;
94     writeFully(&confirmInt, sizeof(confirmInt));
95     uint64_t res;
96     readFully(&res, sizeof(res));
97     return res;
98 }
99 
allocBuffer(size_t minSize)100 void* VirtioGpuPipeStream::allocBuffer(size_t minSize) {
101     size_t allocSize = (m_bufsize < minSize ? minSize : m_bufsize);
102     if (!m_buf) {
103         m_buf = (unsigned char*)malloc(allocSize);
104     } else if (m_bufsize < allocSize) {
105         unsigned char* p = (unsigned char*)realloc(m_buf, allocSize);
106         if (p != NULL) {
107             m_buf = p;
108             m_bufsize = allocSize;
109         } else {
110             mesa_loge("realloc (%zu) failed\n", allocSize);
111             free(m_buf);
112             m_buf = NULL;
113             m_bufsize = 0;
114         }
115     }
116 
117     return m_buf;
118 }
119 
commitBuffer(size_t size)120 int VirtioGpuPipeStream::commitBuffer(size_t size) {
121     if (size == 0) return 0;
122     return writeFully(m_buf, size);
123 }
124 
writeFully(const void * buf,size_t len)125 int VirtioGpuPipeStream::writeFully(const void* buf, size_t len) {
126     // DBG(">> VirtioGpuPipeStream::writeFully %d\n", len);
127     if (!valid()) return -1;
128     if (!buf) {
129         if (len > 0) {
130             // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
131             // in a corrupted state, which is lethal for the emulator.
132             mesa_loge(
133                 "VirtioGpuPipeStream::writeFully failed, buf=NULL, len %zu,"
134                 " lethal error, exiting",
135                 len);
136             abort();
137         }
138         return 0;
139     }
140 
141     size_t res = len;
142     int retval = 0;
143 
144     while (res > 0) {
145         ssize_t stat = transferToHost((const char*)(buf) + (len - res), res);
146         if (stat > 0) {
147             res -= stat;
148             continue;
149         }
150         if (stat == 0) { /* EOF */
151             mesa_loge("VirtioGpuPipeStream::writeFully failed: premature EOF\n");
152             retval = -1;
153             break;
154         }
155         if (errno == EAGAIN) {
156             continue;
157         }
158         retval = stat;
159         mesa_loge("VirtioGpuPipeStream::writeFully failed: %s, lethal error, exiting.\n",
160                   strerror(errno));
161         abort();
162     }
163     // DBG("<< VirtioGpuPipeStream::writeFully %d\n", len );
164     return retval;
165 }
166 
readFully(void * buf,size_t len)167 const unsigned char* VirtioGpuPipeStream::readFully(void* buf, size_t len) {
168     flush();
169 
170     if (!valid()) return NULL;
171     if (!buf) {
172         if (len > 0) {
173             // If len is non-zero, buf must not be NULL. Otherwise the pipe would be
174             // in a corrupted state, which is lethal for the emulator.
175             mesa_loge(
176                 "VirtioGpuPipeStream::readFully failed, buf=NULL, len %zu, lethal"
177                 " error, exiting.",
178                 len);
179             abort();
180         }
181     }
182 
183     size_t res = len;
184     while (res > 0) {
185         ssize_t stat = transferFromHost((char*)(buf) + len - res, res);
186         if (stat == 0) {
187             // client shutdown;
188             return NULL;
189         } else if (stat < 0) {
190             if (errno == EAGAIN) {
191                 continue;
192             } else {
193                 mesa_loge(
194                     "VirtioGpuPipeStream::readFully failed (buf %p, len %zu"
195                     ", res %zu): %s, lethal error, exiting.",
196                     buf, len, res, strerror(errno));
197                 abort();
198             }
199         } else {
200             res -= stat;
201         }
202     }
203     // DBG("<< VirtioGpuPipeStream::readFully %d\n", len);
204     return (const unsigned char*)buf;
205 }
206 
commitBufferAndReadFully(size_t writeSize,void * userReadBufPtr,size_t totalReadSize)207 const unsigned char* VirtioGpuPipeStream::commitBufferAndReadFully(size_t writeSize,
208                                                                    void* userReadBufPtr,
209                                                                    size_t totalReadSize) {
210     return commitBuffer(writeSize) ? nullptr : readFully(userReadBufPtr, totalReadSize);
211 }
212 
read(void * buf,size_t * inout_len)213 const unsigned char* VirtioGpuPipeStream::read(void* buf, size_t* inout_len) {
214     // DBG(">> VirtioGpuPipeStream::read %d\n", *inout_len);
215     if (!valid()) return NULL;
216     if (!buf) {
217         mesa_loge("VirtioGpuPipeStream::read failed, buf=NULL");
218         return NULL;  // do not allow NULL buf in that implementation
219     }
220 
221     int n = recv(buf, *inout_len);
222 
223     if (n > 0) {
224         *inout_len = n;
225         return (const unsigned char*)buf;
226     }
227 
228     // DBG("<< VirtioGpuPipeStream::read %d\n", *inout_len);
229     return NULL;
230 }
231 
recv(void * buf,size_t len)232 int VirtioGpuPipeStream::recv(void* buf, size_t len) {
233     if (!valid()) return -EINVAL;
234     char* p = (char*)buf;
235     int ret = 0;
236     while (len > 0) {
237         int res = transferFromHost(p, len);
238         if (res > 0) {
239             p += res;
240             ret += res;
241             len -= res;
242             continue;
243         }
244         if (res == 0) { /* EOF */
245             break;
246         }
247         if (errno != EAGAIN) {
248             continue;
249         }
250 
251         /* A real error */
252         if (ret == 0) ret = -1;
253         break;
254     }
255     return ret;
256 }
257 
wait()258 void VirtioGpuPipeStream::wait() {
259     int ret = m_resource->wait();
260     if (ret) {
261         mesa_loge("VirtioGpuPipeStream: DRM_IOCTL_VIRTGPU_WAIT failed with %d (%s)\n", errno,
262                   strerror(errno));
263     }
264 
265     m_writtenPos = 0;
266 }
267 
transferToHost(const void * buffer,size_t len)268 ssize_t VirtioGpuPipeStream::transferToHost(const void* buffer, size_t len) {
269     size_t todo = len;
270     size_t done = 0;
271     int ret = EAGAIN;
272 
273     unsigned char* virtioPtr = m_virtio_mapped;
274 
275     const unsigned char* readPtr = reinterpret_cast<const unsigned char*>(buffer);
276 
277     while (done < len) {
278         size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
279 
280         if (toXfer > (kTransferBufferSize - m_writtenPos)) {
281             wait();
282         }
283 
284         memcpy(virtioPtr + m_writtenPos, readPtr, toXfer);
285 
286         ret = m_resource->transferToHost(m_writtenPos, toXfer);
287         if (ret) {
288             mesa_loge("VirtioGpuPipeStream: failed to transferToHost() with errno %d (%s)\n", errno,
289                       strerror(errno));
290             return (ssize_t)ret;
291         }
292 
293         done += toXfer;
294         readPtr += toXfer;
295         todo -= toXfer;
296         m_writtenPos += toXfer;
297     }
298 
299     return len;
300 }
301 
transferFromHost(void * buffer,size_t len)302 ssize_t VirtioGpuPipeStream::transferFromHost(void* buffer, size_t len) {
303     size_t todo = len;
304     size_t done = 0;
305     int ret = EAGAIN;
306 
307     const unsigned char* virtioPtr = m_virtio_mapped;
308     unsigned char* readPtr = reinterpret_cast<unsigned char*>(buffer);
309 
310     if (m_writtenPos) {
311         wait();
312     }
313 
314     while (done < len) {
315         size_t toXfer = todo > kTransferBufferSize ? kTransferBufferSize : todo;
316 
317         ret = m_resource->transferFromHost(0, toXfer);
318         if (ret) {
319             mesa_loge("VirtioGpuPipeStream: failed to transferFromHost() with errno %d (%s)\n",
320                       errno, strerror(errno));
321             return (ssize_t)ret;
322         }
323 
324         wait();
325 
326         memcpy(readPtr, virtioPtr, toXfer);
327 
328         done += toXfer;
329         readPtr += toXfer;
330         todo -= toXfer;
331     }
332 
333     return len;
334 }
335