xref: /openbmc/obmc-ikvm/ikvm_video.cpp (revision 8e909b757a643cbeb4975a2d2fb6f8f6878da51b)
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