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