blob: 39947c65eebf8ec1ab4384a5d706399f6ad70210 [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)));
Jae Hyun Yoof6ed0e72019-03-15 15:21:51 -0700158 restart();
Eddie James90d49582018-12-11 13:22:00 -0600159 return false;
160 }
161
162 if (timings.bt.width != width || timings.bt.height != height)
163 {
164 width = timings.bt.width;
165 height = timings.bt.height;
166
167 if (!width || !height)
168 {
169 log<level::ERR>("Failed to get new resolution",
170 entry("WIDTH=%d", width),
171 entry("HEIGHT=%d", height));
172 elog<Open>(
173 xyz::openbmc_project::Common::File::Open::ERRNO(-EPROTO),
174 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
175 }
176
177 lastFrameIndex = -1;
178 return true;
179 }
180
181 return false;
182}
183
184void Video::resize()
185{
186 int rc;
187 unsigned int i;
188 bool needsResizeCall(false);
189 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
190 v4l2_requestbuffers req;
191
192 if (fd < 0)
193 {
194 return;
195 }
196
197 if (resizeAfterOpen)
198 {
199 resizeAfterOpen = false;
200 return;
201 }
202
203 for (i = 0; i < buffers.size(); ++i)
204 {
205 if (buffers[i].data)
206 {
207 needsResizeCall = true;
208 break;
209 }
210 }
211
212 if (needsResizeCall)
213 {
214 rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
215 if (rc)
216 {
217 log<level::ERR>("Failed to stop streaming",
218 entry("ERROR=%s", strerror(errno)));
219 elog<ReadFailure>(
220 xyz::openbmc_project::Common::Device::ReadFailure::
221 CALLOUT_ERRNO(errno),
222 xyz::openbmc_project::Common::Device::ReadFailure::
223 CALLOUT_DEVICE_PATH(path.c_str()));
224 }
225 }
226
227 for (i = 0; i < buffers.size(); ++i)
228 {
229 if (buffers[i].data)
230 {
231 munmap(buffers[i].data, buffers[i].size);
232 buffers[i].data = nullptr;
233 buffers[i].queued = false;
234 }
235 }
236
237 if (needsResizeCall)
238 {
239 v4l2_dv_timings timings;
240
241 memset(&req, 0, sizeof(v4l2_requestbuffers));
242 req.count = 0;
243 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
244 req.memory = V4L2_MEMORY_MMAP;
245 rc = ioctl(fd, VIDIOC_REQBUFS, &req);
246 if (rc < 0)
247 {
248 log<level::ERR>("Failed to zero streaming buffers",
249 entry("ERROR=%s", strerror(errno)));
250 elog<ReadFailure>(
251 xyz::openbmc_project::Common::Device::ReadFailure::
252 CALLOUT_ERRNO(errno),
253 xyz::openbmc_project::Common::Device::ReadFailure::
254 CALLOUT_DEVICE_PATH(path.c_str()));
255 }
256
257 memset(&timings, 0, sizeof(v4l2_dv_timings));
258 rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings);
259 if (rc < 0)
260 {
261 log<level::ERR>("Failed to query timings",
262 entry("ERROR=%s", strerror(errno)));
263 elog<ReadFailure>(
264 xyz::openbmc_project::Common::Device::ReadFailure::
265 CALLOUT_ERRNO(errno),
266 xyz::openbmc_project::Common::Device::ReadFailure::
267 CALLOUT_DEVICE_PATH(path.c_str()));
268 }
269
270 rc = ioctl(fd, VIDIOC_S_DV_TIMINGS, &timings);
271 if (rc < 0)
272 {
273 log<level::ERR>("Failed to set timings",
274 entry("ERROR=%s", strerror(errno)));
275 elog<ReadFailure>(
276 xyz::openbmc_project::Common::Device::ReadFailure::
277 CALLOUT_ERRNO(errno),
278 xyz::openbmc_project::Common::Device::ReadFailure::
279 CALLOUT_DEVICE_PATH(path.c_str()));
280 }
281
282 buffers.clear();
283 }
284
285 memset(&req, 0, sizeof(v4l2_requestbuffers));
286 req.count = 3;
287 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
288 req.memory = V4L2_MEMORY_MMAP;
289 rc = ioctl(fd, VIDIOC_REQBUFS, &req);
290 if (rc < 0 || req.count < 2)
291 {
292 log<level::ERR>("Failed to request streaming buffers",
293 entry("ERROR=%s", strerror(errno)));
294 elog<ReadFailure>(
295 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
296 errno),
297 xyz::openbmc_project::Common::Device::ReadFailure::
298 CALLOUT_DEVICE_PATH(path.c_str()));
299 }
300
301 buffers.resize(req.count);
302
303 for (i = 0; i < buffers.size(); ++i)
304 {
305 v4l2_buffer buf;
306
307 memset(&buf, 0, sizeof(v4l2_buffer));
308 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
309 buf.memory = V4L2_MEMORY_MMAP;
310 buf.index = i;
311
312 rc = ioctl(fd, VIDIOC_QUERYBUF, &buf);
313 if (rc < 0)
314 {
315 log<level::ERR>("Failed to query buffer",
316 entry("ERROR=%s", strerror(errno)));
317 elog<ReadFailure>(
318 xyz::openbmc_project::Common::Device::ReadFailure::
319 CALLOUT_ERRNO(errno),
320 xyz::openbmc_project::Common::Device::ReadFailure::
321 CALLOUT_DEVICE_PATH(path.c_str()));
322 }
323
324 buffers[i].data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
325 MAP_SHARED, fd, buf.m.offset);
326 if (buffers[i].data == MAP_FAILED)
327 {
328 log<level::ERR>("Failed to mmap buffer",
329 entry("ERROR=%s", strerror(errno)));
330 elog<ReadFailure>(
331 xyz::openbmc_project::Common::Device::ReadFailure::
332 CALLOUT_ERRNO(errno),
333 xyz::openbmc_project::Common::Device::ReadFailure::
334 CALLOUT_DEVICE_PATH(path.c_str()));
335 }
336
337 buffers[i].size = buf.length;
338
339 rc = ioctl(fd, VIDIOC_QBUF, &buf);
340 if (rc < 0)
341 {
342 log<level::ERR>("Failed to queue buffer",
343 entry("ERROR=%s", strerror(errno)));
344 elog<ReadFailure>(
345 xyz::openbmc_project::Common::Device::ReadFailure::
346 CALLOUT_ERRNO(errno),
347 xyz::openbmc_project::Common::Device::ReadFailure::
348 CALLOUT_DEVICE_PATH(path.c_str()));
349 }
350
351 buffers[i].queued = true;
352 }
353
354 rc = ioctl(fd, VIDIOC_STREAMON, &type);
355 if (rc)
356 {
357 log<level::ERR>("Failed to start streaming",
358 entry("ERROR=%s", strerror(errno)));
359 elog<ReadFailure>(
360 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
361 errno),
362 xyz::openbmc_project::Common::Device::ReadFailure::
363 CALLOUT_DEVICE_PATH(path.c_str()));
364 }
365}
366
367void Video::start()
368{
369 int rc;
370 size_t oldHeight = height;
371 size_t oldWidth = width;
372 v4l2_capability cap;
373 v4l2_format fmt;
374 v4l2_streamparm sparm;
375
376 if (fd >= 0)
377 {
378 return;
379 }
380
Jae Hyun Yoo8ec3e232019-04-15 12:10:04 -0700381 input.sendWakeupPacket();
382
Eddie James90d49582018-12-11 13:22:00 -0600383 fd = open(path.c_str(), O_RDWR);
384 if (fd < 0)
385 {
Jae Hyun Yoo8ec3e232019-04-15 12:10:04 -0700386 log<level::ERR>("Failed to open video device",
387 entry("PATH=%s", path.c_str()),
388 entry("ERROR=%s", strerror(errno)));
389 elog<Open>(
390 xyz::openbmc_project::Common::File::Open::ERRNO(errno),
391 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
Eddie James90d49582018-12-11 13:22:00 -0600392 }
393
394 memset(&cap, 0, sizeof(v4l2_capability));
395 rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
396 if (rc < 0)
397 {
398 log<level::ERR>("Failed to query video device capabilities",
399 entry("ERROR=%s", strerror(errno)));
400 elog<ReadFailure>(
401 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
402 errno),
403 xyz::openbmc_project::Common::Device::ReadFailure::
404 CALLOUT_DEVICE_PATH(path.c_str()));
405 }
406
407 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
408 !(cap.capabilities & V4L2_CAP_STREAMING))
409 {
410 log<level::ERR>("Video device doesn't support this application");
411 elog<Open>(
412 xyz::openbmc_project::Common::File::Open::ERRNO(errno),
413 xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
414 }
415
416 memset(&fmt, 0, sizeof(v4l2_format));
417 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
418 rc = ioctl(fd, VIDIOC_G_FMT, &fmt);
419 if (rc < 0)
420 {
421 log<level::ERR>("Failed to query video device format",
422 entry("ERROR=%s", strerror(errno)));
423 elog<ReadFailure>(
424 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
425 errno),
426 xyz::openbmc_project::Common::Device::ReadFailure::
427 CALLOUT_DEVICE_PATH(path.c_str()));
428 }
429
430 memset(&sparm, 0, sizeof(v4l2_streamparm));
431 sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
432 sparm.parm.capture.timeperframe.numerator = 1;
433 sparm.parm.capture.timeperframe.denominator = frameRate;
434 rc = ioctl(fd, VIDIOC_S_PARM, &sparm);
435 if (rc < 0)
436 {
437 log<level::WARNING>("Failed to set video device frame rate",
438 entry("ERROR=%s", strerror(errno)));
439 }
440
441 height = fmt.fmt.pix.height;
442 width = fmt.fmt.pix.width;
443
444 resize();
445
446 if (oldHeight != height || oldWidth != width)
447 {
448 resizeAfterOpen = true;
449 }
450}
451
452void Video::stop()
453{
454 int rc;
455 unsigned int i;
456 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
457
458 if (fd < 0)
459 {
460 return;
461 }
462
463 lastFrameIndex = -1;
464
465 rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
466 if (rc)
467 {
468 log<level::ERR>("Failed to stop streaming",
469 entry("ERROR=%s", strerror(errno)));
470 }
471
472 for (i = 0; i < buffers.size(); ++i)
473 {
474 if (buffers[i].data)
475 {
476 munmap(buffers[i].data, buffers[i].size);
477 buffers[i].data = nullptr;
478 buffers[i].queued = false;
479 }
480 }
481
482 close(fd);
483 fd = -1;
Eddie James21b177e2018-12-11 13:14:46 -0600484}
485
486} // namespace ikvm