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