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