blob: f373631e4a1189634e37f9d7ecd4e2aef1ee2c84 [file] [log] [blame]
/*
* Copyright (C) 2013-2017 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cros-camera/v4l2_device.h"
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "cros-camera/common.h"
namespace cros {
V4L2Device::V4L2Device(const std::string name) : name_(name), fd_(-1) {}
V4L2Device::~V4L2Device() {
VLOGF_ENTER();
if (IsOpened()) {
LOGF(WARNING) << "Destroying a device object not closed, closing first";
Close();
}
}
int V4L2Device::Open(int flags) {
VLOGF_ENTER();
if (IsOpened()) {
LOGF(WARNING) << "Device is already opened";
return 0;
}
struct stat st = {};
if (stat(name_.c_str(), &st) == -1) {
PLOGF(ERROR) << "Error stat video device " << name_.c_str();
return -ENODEV;
}
if (!S_ISCHR(st.st_mode)) {
LOGF(ERROR) << name_.c_str() << " is not a device";
return -ENODEV;
}
fd_ = ::open(name_.c_str(), flags);
if (fd_ < 0) {
PLOGF(ERROR) << "Error opening video device " << name_.c_str();
return -errno;
}
return 0;
}
int V4L2Device::Close() {
VLOGF_ENTER();
if (!IsOpened()) {
LOGF(WARNING) << "Device is not opened!";
return -EINVAL;
}
int ret = ::close(fd_);
if (ret < 0) {
PLOGF(ERROR) << "Error closing video device " << name_.c_str();
return ret;
}
fd_ = -1;
return 0;
}
int V4L2Device::SubscribeEvent(int event) {
VLOGF_ENTER();
if (!IsOpened()) {
LOGF(ERROR) << "Device " << name_ << " already closed. Do nothing.";
return -1;
}
struct v4l2_event_subscription sub = {};
sub.type = event;
int ret = ::ioctl(fd_, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (ret < 0) {
PLOGF(ERROR) << "Error subscribing event 0x" << std::hex << event;
return ret;
}
return ret;
}
int V4L2Device::UnsubscribeEvent(int event) {
VLOGF_ENTER();
if (!IsOpened()) {
LOGF(ERROR) << "Device " << name_ << " already closed. Do nothing.";
return -1;
}
struct v4l2_event_subscription sub = {};
sub.type = event;
int ret = ::ioctl(fd_, VIDIOC_UNSUBSCRIBE_EVENT, &sub);
if (ret < 0) {
PLOGF(ERROR) << "Error unsubscribing event 0x" << std::hex << event;
return ret;
}
return ret;
}
int V4L2Device::DequeueEvent(struct v4l2_event* event) {
VLOGF_ENTER();
if (!event) {
return -EINVAL;
}
if (!IsOpened()) {
LOGF(ERROR) << "Device " << name_ << " already closed. Do nothing.";
return -1;
}
int ret = ::ioctl(fd_, VIDIOC_DQEVENT, event);
if (ret < 0) {
LOGF(ERROR) << "error dequeuing event";
return ret;
}
return ret;
}
int V4L2Device::SetControl(struct v4l2_control* control) {
if (!IsOpened()) {
LOGF(ERROR) << "Invalid device state (CLOSED)";
return -EINVAL;
}
if (!control) {
LOGF(ERROR) << "Null pointer of control";
return -EINVAL;
}
return ::ioctl(fd_, VIDIOC_S_CTRL, control);
}
int V4L2Device::SetControl(struct v4l2_ext_control* ext_control) {
if (!IsOpened()) {
LOGF(ERROR) << "Invalid device state (CLOSED)";
return -EINVAL;
}
if (!ext_control) {
LOGF(ERROR) << "Null pointer of ext_control";
return -EINVAL;
}
struct v4l2_ext_controls controls = {};
controls.ctrl_class = V4L2_CTRL_ID2CLASS(ext_control->id);
controls.count = 1;
controls.controls = ext_control;
return ::ioctl(fd_, VIDIOC_S_EXT_CTRLS, &controls);
}
int V4L2Device::SetControl(int id, int32_t value) {
VLOGF_ENTER();
VLOGF(2) << "Setting attribute " << id << " to " << value;
int ret = 0;
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
ext_control.value = value;
ret = SetControl(&ext_control);
if (ret != 0) {
PLOGF(ERROR) << "Failed to set value " << value << " for control " << id
<< " on device " << name_.c_str();
}
return ret;
}
int V4L2Device::SetControl(int id, int64_t value) {
VLOGF_ENTER();
VLOGF(2) << "Setting attribute " << id << " to " << value;
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
ext_control.value64 = value;
int ret = SetControl(&ext_control);
if (ret != 0) {
PLOGF(ERROR) << "Failed to set value " << value << " for control " << id
<< " on device " << name_.c_str();
}
return ret;
}
int V4L2Device::SetControl(int id, const std::string value) {
VLOGF_ENTER();
VLOGF(2) << "Setting attribute " << id << " to " << value;
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
ext_control.string = const_cast<char*>(value.c_str());
int ret = SetControl(&ext_control);
if (ret != 0) {
PLOGF(ERROR) << "Failed to set value " << value << " for control " << id
<< " on device " << name_.c_str();
}
return ret;
}
int V4L2Device::GetControl(struct v4l2_ext_control* ext_control) {
if (!IsOpened()) {
LOGF(ERROR) << "Invalid state device (CLOSED)";
return -EINVAL;
}
struct v4l2_ext_controls controls = {};
controls.ctrl_class = V4L2_CTRL_ID2CLASS(ext_control->id);
controls.count = 1;
controls.controls = ext_control;
int ret = ::ioctl(fd_, VIDIOC_G_EXT_CTRLS, &controls);
if (ret != 0) {
PLOGF(ERROR) << "Failed to get value for control (" << ext_control->id
<< ") on device " << name_.c_str();
return ret;
}
return 0;
}
int V4L2Device::GetControl(int id, int32_t* value) {
VLOGF_ENTER();
if (!value) {
return -EINVAL;
}
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
int ret = GetControl(&ext_control);
if (ret == 0) {
*value = ext_control.value;
}
return ret;
}
int V4L2Device::GetControl(int id, int64_t* value) {
VLOGF_ENTER();
if (!value) {
return -EINVAL;
}
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
int ret = GetControl(&ext_control);
if (ret == 0) {
*value = ext_control.value64;
}
return ret;
}
int V4L2Device::GetControl(int id, std::string* value) {
VLOGF_ENTER();
if (!value) {
return -EINVAL;
}
struct v4l2_ext_control ext_control = {};
ext_control.id = id;
int ret = GetControl(&ext_control);
if (ret == 0) {
*value = ext_control.string;
}
return ret;
}
int V4L2Device::QueryMenu(v4l2_querymenu* menu) {
VLOGF_ENTER();
if (!menu) {
return -EINVAL;
}
if (fd_ == -1) {
LOGF(ERROR) << "Invalid state device (CLOSED)";
return -EINVAL;
}
int ret = ::ioctl(fd_, VIDIOC_QUERYMENU, menu);
if (ret != 0) {
PLOGF(ERROR) << "Failed to get values for query menu (" << menu->id
<< ") on device" << name_.c_str();
}
return ret;
}
int V4L2Device::QueryControl(v4l2_queryctrl* control) {
VLOGF_ENTER();
if (!control) {
return -EINVAL;
}
if (fd_ == -1) {
LOGF(ERROR) << "Invalid state device (CLOSED)";
return -EINVAL;
}
int ret = ::ioctl(fd_, VIDIOC_QUERYCTRL, control);
if (ret != 0) {
PLOGF(ERROR) << "Failed to get values for query control (" << control->id
<< ") on device " << name_.c_str();
}
return ret;
}
int V4L2Device::Poll(int timeout) {
VLOGF_ENTER();
struct pollfd pfd = {0};
int ret(0);
if (fd_ == -1) {
LOGF(ERROR) << "Device " << name_.c_str() << " already closed. Do nothing.";
return -1;
}
pfd.fd = fd_;
pfd.events = POLLPRI | POLLIN | POLLERR;
ret = ::poll(&pfd, 1, timeout);
if (ret < 0) {
LOGF(ERROR) << "poll error ret=" << ret << ", mFd= " << fd_
<< ", error:" << strerror(errno);
return ret;
}
if (pfd.revents & POLLERR) {
LOGF(ERROR) << "received POLLERR";
return -1;
}
return ret;
}
V4L2DevicePoller::V4L2DevicePoller(std::vector<V4L2Device*> devices,
int flush_fd)
: devices_(std::move(devices)),
flush_fd_(flush_fd),
poll_fds_(devices_.size() + ((flush_fd == -1) ? 0 : 1)) {
for (size_t i = 0; i < devices_.size(); i++) {
if (!devices_[i]) {
LOGF(ERROR) << "Invalid device at index " << i;
poll_fds_.resize(0);
return;
}
poll_fds_[i].fd = devices_[i]->fd_;
poll_fds_[i].revents = 0;
}
if (flush_fd_ != -1) {
poll_fds_.back().fd = flush_fd_;
poll_fds_.back().events = POLLIN | POLLPRI;
}
}
int V4L2DevicePoller::Poll(int timeout_ms,
int events,
std::vector<V4L2Device*>* ready_devices) {
VLOGF_ENTER();
if (poll_fds_.empty()) {
return -EINVAL;
}
for (size_t i = 0; i < devices_.size(); i++) {
poll_fds_[i].events = events;
}
int ret = ::poll(poll_fds_.data(), poll_fds_.size(), timeout_ms);
if (ret <= 0) {
for (size_t i = 0; i < devices_.size(); i++) {
PLOGF(ERROR) << "Device " << devices_[i]->name_ << " poll failed ("
<< ((ret == 0) ? "timeout)" : "error)");
}
return ret;
}
// check first the flush
if (flush_fd_ != -1 && (poll_fds_.back().revents & (POLLIN | POLLPRI))) {
VLOGF(1) << "Poll returning from flush";
return ret;
}
bool is_pollerr = false;
for (size_t i = 0; i < devices_.size(); i++) {
if (poll_fds_[i].revents & POLLERR) {
LOGF(ERROR) << "Device " << devices_[i]->name_ << " received POLLERR";
is_pollerr = true;
}
}
if (is_pollerr) {
return -1;
}
if (ready_devices != nullptr) {
// check other active devices.
for (size_t i = 0; i < devices_.size(); i++) {
// return nodes that have data available
if (poll_fds_[i].revents & events) {
ready_devices->push_back(devices_[i]);
}
}
}
return ret;
}
} // namespace cros