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