blob: ed5ed92ef1312d64d8ff3950bfaad09d5bfdd4ce [file] [log] [blame]
Eddie James21b177e2018-12-11 13:14:46 -06001#include "ikvm_video.hpp"
2
Eddie James90d49582018-12-11 13:22:00 -06003#include <err.h>
4#include <errno.h>
5#include <fcntl.h>
6#include <linux/videodev2.h>
7#include <poll.h>
8#include <sys/ioctl.h>
9#include <sys/mman.h>
10#include <sys/select.h>
11#include <sys/stat.h>
12#include <sys/time.h>
13#include <sys/types.h>
14#include <unistd.h>
15
16#include <phosphor-logging/elog-errors.hpp>
17#include <phosphor-logging/elog.hpp>
18#include <phosphor-logging/log.hpp>
19#include <xyz/openbmc_project/Common/Device/error.hpp>
20#include <xyz/openbmc_project/Common/File/error.hpp>
21
Eddie James21b177e2018-12-11 13:14:46 -060022namespace ikvm
23{
24
Eddie James90d49582018-12-11 13:22:00 -060025const int Video::bitsPerSample(8);
26const int Video::bytesPerPixel(4);
27const int Video::samplesPerPixel(3);
28
29using namespace phosphor::logging;
30using namespace sdbusplus::xyz::openbmc_project::Common::File::Error;
31using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
32
Eddie James21b177e2018-12-11 13:14:46 -060033Video::Video(const std::string& p, Input& input, int fr) :
Eddie James90d49582018-12-11 13:22:00 -060034 resizeAfterOpen(false), fd(-1), frameRate(fr), lastFrameIndex(-1),
Eddie James21b177e2018-12-11 13:14:46 -060035 height(600), width(800), input(input), path(p)
36{
37}
38
39Video::~Video()
40{
Eddie James90d49582018-12-11 13:22:00 -060041 stop();
42}
43
44char* Video::getData()
45{
46 if (lastFrameIndex >= 0)
47 {
48 return (char*)buffers[lastFrameIndex].data;
49 }
50
51 return nullptr;
52}
53
54void Video::getFrame()
55{
56 int rc(0);
57 int fd_flags;
58 v4l2_buffer buf;
59 fd_set fds;
60 timeval tv;
61
62 if (fd < 0)
63 {
64 return;
65 }
66
67 FD_ZERO(&fds);
68 FD_SET(fd, &fds);
69
70 tv.tv_sec = 1;
71 tv.tv_usec = 0;
72
73 memset(&buf, 0, sizeof(v4l2_buffer));
74 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
75 buf.memory = V4L2_MEMORY_MMAP;
76
77 // Switch to non-blocking in order to safely dequeue all buffers; if the
78 // video signal is lost while blocking to dequeue, the video driver may
79 // wait forever if signal is not re-acquired
80 fd_flags = fcntl(fd, F_GETFL);
81 fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
82
83 rc = select(fd + 1, &fds, NULL, NULL, &tv);
84 if (rc > 0)
85 {
86 do
87 {
88 rc = ioctl(fd, VIDIOC_DQBUF, &buf);
89 if (rc >= 0)
90 {
91 buffers[buf.index].queued = false;
92
93 if (!(buf.flags & V4L2_BUF_FLAG_ERROR))
94 {
95 lastFrameIndex = buf.index;
96 buffers[lastFrameIndex].payload = buf.bytesused;
97 break;
98 }
99 else
100 {
101 buffers[buf.index].payload = 0;
102 }
103 }
104 } while (rc >= 0);
105 }
106
107 fcntl(fd, F_SETFL, fd_flags);
108
109 for (unsigned int i = 0; i < buffers.size(); ++i)
110 {
111 if (i == (unsigned int)lastFrameIndex)
112 {
113 continue;
114 }
115
116 if (!buffers[i].queued)
117 {
118 memset(&buf, 0, sizeof(v4l2_buffer));
119 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
120 buf.memory = V4L2_MEMORY_MMAP;
121 buf.index = i;
122
123 rc = ioctl(fd, VIDIOC_QBUF, &buf);
124 if (rc)
125 {
126 log<level::ERR>("Failed to queue buffer",
127 entry("ERROR=%s", strerror(errno)));
128 }
129 else
130 {
131 buffers[i].queued = true;
132 }
133 }
134 }
135}
136
137bool Video::needsResize()
138{
139 int rc;
140 v4l2_dv_timings timings;
141
142 if (fd < 0)
143 {
144 return false;
145 }
146
147 if (resizeAfterOpen)
148 {
149 return true;
150 }
151
152 memset(&timings, 0, sizeof(v4l2_dv_timings));
153 rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings);
154 if (rc < 0)
155 {
156 log<level::ERR>("Failed to query timings",
157 entry("ERROR=%s", strerror(errno)));
158 return false;
159 }
160
161 if (timings.bt.width != width || timings.bt.height != height)
162 {
163 width = timings.bt.width;
164 height = timings.bt.height;
165
166 if (!width || !height)
167 {
168 log<level::ERR>("Failed to get new resolution",
169 entry("WIDTH=%d", width),
170 entry("HEIGHT=%d", height));
171 elog<Open>(
172 xyz::openbmc_project::Common::File::Open::ERRNO(-EPROTO),
173 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
174 }
175
176 lastFrameIndex = -1;
177 return true;
178 }
179
180 return false;
181}
182
183void Video::resize()
184{
185 int rc;
186 unsigned int i;
187 bool needsResizeCall(false);
188 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
189 v4l2_requestbuffers req;
190
191 if (fd < 0)
192 {
193 return;
194 }
195
196 if (resizeAfterOpen)
197 {
198 resizeAfterOpen = false;
199 return;
200 }
201
202 for (i = 0; i < buffers.size(); ++i)
203 {
204 if (buffers[i].data)
205 {
206 needsResizeCall = true;
207 break;
208 }
209 }
210
211 if (needsResizeCall)
212 {
213 rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
214 if (rc)
215 {
216 log<level::ERR>("Failed to stop streaming",
217 entry("ERROR=%s", strerror(errno)));
218 elog<ReadFailure>(
219 xyz::openbmc_project::Common::Device::ReadFailure::
220 CALLOUT_ERRNO(errno),
221 xyz::openbmc_project::Common::Device::ReadFailure::
222 CALLOUT_DEVICE_PATH(path.c_str()));
223 }
224 }
225
226 for (i = 0; i < buffers.size(); ++i)
227 {
228 if (buffers[i].data)
229 {
230 munmap(buffers[i].data, buffers[i].size);
231 buffers[i].data = nullptr;
232 buffers[i].queued = false;
233 }
234 }
235
236 if (needsResizeCall)
237 {
238 v4l2_dv_timings timings;
239
240 memset(&req, 0, sizeof(v4l2_requestbuffers));
241 req.count = 0;
242 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
243 req.memory = V4L2_MEMORY_MMAP;
244 rc = ioctl(fd, VIDIOC_REQBUFS, &req);
245 if (rc < 0)
246 {
247 log<level::ERR>("Failed to zero streaming buffers",
248 entry("ERROR=%s", strerror(errno)));
249 elog<ReadFailure>(
250 xyz::openbmc_project::Common::Device::ReadFailure::
251 CALLOUT_ERRNO(errno),
252 xyz::openbmc_project::Common::Device::ReadFailure::
253 CALLOUT_DEVICE_PATH(path.c_str()));
254 }
255
256 memset(&timings, 0, sizeof(v4l2_dv_timings));
257 rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings);
258 if (rc < 0)
259 {
260 log<level::ERR>("Failed to query timings",
261 entry("ERROR=%s", strerror(errno)));
262 elog<ReadFailure>(
263 xyz::openbmc_project::Common::Device::ReadFailure::
264 CALLOUT_ERRNO(errno),
265 xyz::openbmc_project::Common::Device::ReadFailure::
266 CALLOUT_DEVICE_PATH(path.c_str()));
267 }
268
269 rc = ioctl(fd, VIDIOC_S_DV_TIMINGS, &timings);
270 if (rc < 0)
271 {
272 log<level::ERR>("Failed to set timings",
273 entry("ERROR=%s", strerror(errno)));
274 elog<ReadFailure>(
275 xyz::openbmc_project::Common::Device::ReadFailure::
276 CALLOUT_ERRNO(errno),
277 xyz::openbmc_project::Common::Device::ReadFailure::
278 CALLOUT_DEVICE_PATH(path.c_str()));
279 }
280
281 buffers.clear();
282 }
283
284 memset(&req, 0, sizeof(v4l2_requestbuffers));
285 req.count = 3;
286 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
287 req.memory = V4L2_MEMORY_MMAP;
288 rc = ioctl(fd, VIDIOC_REQBUFS, &req);
289 if (rc < 0 || req.count < 2)
290 {
291 log<level::ERR>("Failed to request streaming buffers",
292 entry("ERROR=%s", strerror(errno)));
293 elog<ReadFailure>(
294 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
295 errno),
296 xyz::openbmc_project::Common::Device::ReadFailure::
297 CALLOUT_DEVICE_PATH(path.c_str()));
298 }
299
300 buffers.resize(req.count);
301
302 for (i = 0; i < buffers.size(); ++i)
303 {
304 v4l2_buffer buf;
305
306 memset(&buf, 0, sizeof(v4l2_buffer));
307 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
308 buf.memory = V4L2_MEMORY_MMAP;
309 buf.index = i;
310
311 rc = ioctl(fd, VIDIOC_QUERYBUF, &buf);
312 if (rc < 0)
313 {
314 log<level::ERR>("Failed to query buffer",
315 entry("ERROR=%s", strerror(errno)));
316 elog<ReadFailure>(
317 xyz::openbmc_project::Common::Device::ReadFailure::
318 CALLOUT_ERRNO(errno),
319 xyz::openbmc_project::Common::Device::ReadFailure::
320 CALLOUT_DEVICE_PATH(path.c_str()));
321 }
322
323 buffers[i].data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
324 MAP_SHARED, fd, buf.m.offset);
325 if (buffers[i].data == MAP_FAILED)
326 {
327 log<level::ERR>("Failed to mmap buffer",
328 entry("ERROR=%s", strerror(errno)));
329 elog<ReadFailure>(
330 xyz::openbmc_project::Common::Device::ReadFailure::
331 CALLOUT_ERRNO(errno),
332 xyz::openbmc_project::Common::Device::ReadFailure::
333 CALLOUT_DEVICE_PATH(path.c_str()));
334 }
335
336 buffers[i].size = buf.length;
337
338 rc = ioctl(fd, VIDIOC_QBUF, &buf);
339 if (rc < 0)
340 {
341 log<level::ERR>("Failed to queue buffer",
342 entry("ERROR=%s", strerror(errno)));
343 elog<ReadFailure>(
344 xyz::openbmc_project::Common::Device::ReadFailure::
345 CALLOUT_ERRNO(errno),
346 xyz::openbmc_project::Common::Device::ReadFailure::
347 CALLOUT_DEVICE_PATH(path.c_str()));
348 }
349
350 buffers[i].queued = true;
351 }
352
353 rc = ioctl(fd, VIDIOC_STREAMON, &type);
354 if (rc)
355 {
356 log<level::ERR>("Failed to start streaming",
357 entry("ERROR=%s", strerror(errno)));
358 elog<ReadFailure>(
359 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
360 errno),
361 xyz::openbmc_project::Common::Device::ReadFailure::
362 CALLOUT_DEVICE_PATH(path.c_str()));
363 }
364}
365
366void Video::start()
367{
368 int rc;
369 size_t oldHeight = height;
370 size_t oldWidth = width;
371 v4l2_capability cap;
372 v4l2_format fmt;
373 v4l2_streamparm sparm;
374
375 if (fd >= 0)
376 {
377 return;
378 }
379
380 fd = open(path.c_str(), O_RDWR);
381 if (fd < 0)
382 {
Jae Hyun Yoo7dfac9f2019-01-15 10:14:59 -0800383 input.sendWakeupPacket();
Eddie James90d49582018-12-11 13:22:00 -0600384
385 fd = open(path.c_str(), O_RDWR);
386 if (fd < 0)
387 {
388 log<level::ERR>("Failed to open video device",
389 entry("PATH=%s", path.c_str()),
390 entry("ERROR=%s", strerror(errno)));
391 elog<Open>(
392 xyz::openbmc_project::Common::File::Open::ERRNO(errno),
393 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
394 }
395 }
396
397 memset(&cap, 0, sizeof(v4l2_capability));
398 rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
399 if (rc < 0)
400 {
401 log<level::ERR>("Failed to query video device capabilities",
402 entry("ERROR=%s", strerror(errno)));
403 elog<ReadFailure>(
404 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
405 errno),
406 xyz::openbmc_project::Common::Device::ReadFailure::
407 CALLOUT_DEVICE_PATH(path.c_str()));
408 }
409
410 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
411 !(cap.capabilities & V4L2_CAP_STREAMING))
412 {
413 log<level::ERR>("Video device doesn't support this application");
414 elog<Open>(
415 xyz::openbmc_project::Common::File::Open::ERRNO(errno),
416 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
417 }
418
419 memset(&fmt, 0, sizeof(v4l2_format));
420 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
421 rc = ioctl(fd, VIDIOC_G_FMT, &fmt);
422 if (rc < 0)
423 {
424 log<level::ERR>("Failed to query video device format",
425 entry("ERROR=%s", strerror(errno)));
426 elog<ReadFailure>(
427 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
428 errno),
429 xyz::openbmc_project::Common::Device::ReadFailure::
430 CALLOUT_DEVICE_PATH(path.c_str()));
431 }
432
433 memset(&sparm, 0, sizeof(v4l2_streamparm));
434 sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
435 sparm.parm.capture.timeperframe.numerator = 1;
436 sparm.parm.capture.timeperframe.denominator = frameRate;
437 rc = ioctl(fd, VIDIOC_S_PARM, &sparm);
438 if (rc < 0)
439 {
440 log<level::WARNING>("Failed to set video device frame rate",
441 entry("ERROR=%s", strerror(errno)));
442 }
443
444 height = fmt.fmt.pix.height;
445 width = fmt.fmt.pix.width;
446
447 resize();
448
449 if (oldHeight != height || oldWidth != width)
450 {
451 resizeAfterOpen = true;
452 }
453}
454
455void Video::stop()
456{
457 int rc;
458 unsigned int i;
459 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
460
461 if (fd < 0)
462 {
463 return;
464 }
465
466 lastFrameIndex = -1;
467
468 rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
469 if (rc)
470 {
471 log<level::ERR>("Failed to stop streaming",
472 entry("ERROR=%s", strerror(errno)));
473 }
474
475 for (i = 0; i < buffers.size(); ++i)
476 {
477 if (buffers[i].data)
478 {
479 munmap(buffers[i].data, buffers[i].size);
480 buffers[i].data = nullptr;
481 buffers[i].queued = false;
482 }
483 }
484
485 close(fd);
486 fd = -1;
Eddie James21b177e2018-12-11 13:14:46 -0600487}
488
489} // namespace ikvm