xref: /openbmc/obmc-ikvm/ikvm_video.cpp (revision 8e909b75)
1 #include "ikvm_video.hpp"
2 
3 #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 
22 namespace ikvm
23 {
24 
25 const int Video::bitsPerSample(8);
26 const int Video::bytesPerPixel(4);
27 const int Video::samplesPerPixel(3);
28 
29 using namespace phosphor::logging;
30 using namespace sdbusplus::xyz::openbmc_project::Common::File::Error;
31 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
32 
Video(const std::string & p,Input & input,int fr,int sub)33 Video::Video(const std::string& p, Input& input, int fr, int sub) :
34     resizeAfterOpen(false), timingsError(false), fd(-1), frameRate(fr),
35     lastFrameIndex(-1), height(600), width(800), subSampling(sub), input(input),
36     path(p), pixelformat(V4L2_PIX_FMT_JPEG)
37 {}
38 
~Video()39 Video::~Video()
40 {
41     stop();
42 }
43 
getData()44 char* Video::getData()
45 {
46     if (lastFrameIndex >= 0)
47     {
48         return (char*)buffers[lastFrameIndex].data;
49     }
50 
51     return nullptr;
52 }
53 
getFrame()54 void 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 
needsResize()137 bool 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         if (!timingsError)
157         {
158             log<level::ERR>("Failed to query timings",
159                             entry("ERROR=%s", strerror(errno)));
160             timingsError = true;
161         }
162 
163         restart();
164         return false;
165     }
166     else
167     {
168         timingsError = false;
169     }
170 
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 
resize()193 void 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         {
270             log<level::ERR>("Failed to query timings, restart",
271                             entry("ERROR=%s", strerror(errno)));
272             restart();
273             return;
274         }
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 
start()373 void 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     v4l2_control ctrl;
382 
383     if (fd >= 0)
384     {
385         return;
386     }
387 
388     input.sendWakeupPacket();
389 
390     fd = open(path.c_str(), O_RDWR);
391     if (fd < 0)
392     {
393         log<level::ERR>("Failed to open video device",
394                         entry("PATH=%s", path.c_str()),
395                         entry("ERROR=%s", strerror(errno)));
396         elog<Open>(
397             xyz::openbmc_project::Common::File::Open::ERRNO(errno),
398             xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
399     }
400 
401     memset(&cap, 0, sizeof(v4l2_capability));
402     rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
403     if (rc < 0)
404     {
405         log<level::ERR>("Failed to query video device capabilities",
406                         entry("ERROR=%s", strerror(errno)));
407         elog<ReadFailure>(
408             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
409                 errno),
410             xyz::openbmc_project::Common::Device::ReadFailure::
411                 CALLOUT_DEVICE_PATH(path.c_str()));
412     }
413 
414     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
415         !(cap.capabilities & V4L2_CAP_STREAMING))
416     {
417         log<level::ERR>("Video device doesn't support this application");
418         elog<Open>(
419             xyz::openbmc_project::Common::File::Open::ERRNO(errno),
420             xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
421     }
422 
423     memset(&fmt, 0, sizeof(v4l2_format));
424     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
425     rc = ioctl(fd, VIDIOC_G_FMT, &fmt);
426     if (rc < 0)
427     {
428         log<level::ERR>("Failed to query video device format",
429                         entry("ERROR=%s", strerror(errno)));
430         elog<ReadFailure>(
431             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
432                 errno),
433             xyz::openbmc_project::Common::Device::ReadFailure::
434                 CALLOUT_DEVICE_PATH(path.c_str()));
435     }
436 
437     memset(&sparm, 0, sizeof(v4l2_streamparm));
438     sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
439     sparm.parm.capture.timeperframe.numerator = 1;
440     sparm.parm.capture.timeperframe.denominator = frameRate;
441     rc = ioctl(fd, VIDIOC_S_PARM, &sparm);
442     if (rc < 0)
443     {
444         log<level::WARNING>("Failed to set video device frame rate",
445                             entry("ERROR=%s", strerror(errno)));
446     }
447 
448     ctrl.id = V4L2_CID_JPEG_CHROMA_SUBSAMPLING;
449     ctrl.value = subSampling ? V4L2_JPEG_CHROMA_SUBSAMPLING_420
450                              : V4L2_JPEG_CHROMA_SUBSAMPLING_444;
451     rc = ioctl(fd, VIDIOC_S_CTRL, &ctrl);
452     if (rc < 0)
453     {
454         log<level::WARNING>("Failed to set video jpeg subsampling",
455                             entry("ERROR=%s", strerror(errno)));
456     }
457 
458     height = fmt.fmt.pix.height;
459     width = fmt.fmt.pix.width;
460     pixelformat = fmt.fmt.pix.pixelformat;
461 
462     if (pixelformat != V4L2_PIX_FMT_RGB24 && pixelformat != V4L2_PIX_FMT_JPEG)
463     {
464         log<level::ERR>("Pixel Format not supported",
465                         entry("PIXELFORMAT=%d", pixelformat));
466     }
467 
468     resize();
469 
470     if (oldHeight != height || oldWidth != width)
471     {
472         resizeAfterOpen = true;
473     }
474 }
475 
stop()476 void Video::stop()
477 {
478     int rc;
479     unsigned int i;
480     v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
481 
482     if (fd < 0)
483     {
484         return;
485     }
486 
487     lastFrameIndex = -1;
488 
489     rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
490     if (rc)
491     {
492         log<level::ERR>("Failed to stop streaming",
493                         entry("ERROR=%s", strerror(errno)));
494     }
495 
496     for (i = 0; i < buffers.size(); ++i)
497     {
498         if (buffers[i].data)
499         {
500             munmap(buffers[i].data, buffers[i].size);
501             buffers[i].data = nullptr;
502             buffers[i].queued = false;
503         }
504     }
505 
506     close(fd);
507     fd = -1;
508 }
509 
510 } // namespace ikvm
511