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