From patchwork Thu Jan 17 21:27:02 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 263 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 378E460B2D for ; Thu, 17 Jan 2019 22:27:13 +0100 (CET) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C05C558; Thu, 17 Jan 2019 22:27:12 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1547760432; bh=A8JNgNUlYnLyREN59ZNVcnvf24lef7ofOfejK95ChjU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XbbONCZV2UOVl8ChzWm6Fa/sExznugzIF9buTCIC+JyAKc0Z8gi9GR2WfwpURhxy4 ZbYm1mVhwA78e/ILqXyQEUt8d1DIWwOb3nce/D8QyKXi4dYho21Wn7hVSSl6Bwvj16 xkItOLnhjxUzoCerA4/ad2WJnaFxi0uxfwkMQX1s= From: Kieran Bingham To: LibCamera Devel Date: Thu, 17 Jan 2019 21:27:02 +0000 Message-Id: <20190117212703.24047-2-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190117212703.24047-1-kieran.bingham@ideasonboard.com> References: <20190117212703.24047-1-kieran.bingham@ideasonboard.com> Subject: [libcamera-devel] [PATCH v4 1/2] libcamera: Add V4L2 Device object X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 17 Jan 2019 21:27:13 -0000 Provide a helper V4L2 device object capable of interacting with the V4L2 Linux Kernel APIs. Signed-off-by: Kieran Bingham Reviewed-by: Niklas Söderlund --- src/libcamera/include/v4l2_device.h | 60 +++++++++ src/libcamera/meson.build | 2 + src/libcamera/v4l2_device.cpp | 186 ++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 src/libcamera/include/v4l2_device.h create mode 100644 src/libcamera/v4l2_device.cpp diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h new file mode 100644 index 000000000000..474c05b58cef --- /dev/null +++ b/src/libcamera/include/v4l2_device.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * v4l2_device.h - V4L2 Device + */ +#ifndef __LIBCAMERA_V4L2_DEVICE_H__ +#define __LIBCAMERA_V4L2_DEVICE_H__ + +#include + +#include + +namespace libcamera { + +struct V4L2Capability final : v4l2_capability { + const char *driver() const + { + return reinterpret_cast(v4l2_capability::driver); + } + const char *card() const + { + return reinterpret_cast(v4l2_capability::card); + } + const char *bus_info() const + { + return reinterpret_cast(v4l2_capability::bus_info); + } + + bool isCapture() const { return capabilities & V4L2_CAP_VIDEO_CAPTURE; } + bool isOutput() const { return capabilities & V4L2_CAP_VIDEO_OUTPUT; } + bool hasStreaming() const { return capabilities & V4L2_CAP_STREAMING; } +}; + +class V4L2Device +{ +public: + V4L2Device(const std::string &devnode); + V4L2Device(const V4L2Device &) = delete; + ~V4L2Device(); + + void operator=(const V4L2Device &) = delete; + + int open(); + bool isOpen() const; + void close(); + + const char *driverName() const { return caps_.driver(); } + const char *deviceName() const { return caps_.card(); } + const char *busName() const { return caps_.bus_info(); } + +private: + std::string devnode_; + int fd_; + V4L2Capability caps_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index abf9a71d4172..f9f25c0ecf15 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -11,6 +11,7 @@ libcamera_sources = files([ 'pipeline_handler.cpp', 'signal.cpp', 'timer.cpp', + 'v4l2_device.cpp', ]) libcamera_headers = files([ @@ -21,6 +22,7 @@ libcamera_headers = files([ 'include/media_object.h', 'include/pipeline_handler.h', 'include/utils.h', + 'include/v4l2_device.h', ]) libcamera_internal_includes = include_directories('include') diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp new file mode 100644 index 000000000000..54b53de37510 --- /dev/null +++ b/src/libcamera/v4l2_device.cpp @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * v4l2_device.cpp - V4L2 Device + */ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "v4l2_device.h" + +/** + * \file v4l2_device.h + * \brief V4L2 Device API + */ +namespace libcamera { + +/** + * \struct V4L2Capability + * \brief struct v4l2_capability object wrapper and helpers + * + * The V4L2Capability structure manages the information returned by the + * VIDIOC_QUERYCAP ioctl. + */ + +/** + * \fn const char *V4L2Capability::driver() + * \brief Retrieve the driver module name + * \return The string containing the name of the driver module + */ + +/** + * \fn const char *V4L2Capability::card() + * \brief Retrieve the device card name + * \return The string containing the device name + */ + +/** + * \fn const char *V4L2Capability::bus_info() + * \brief Retrieve the location of the device in the system + * \return The string containing the device location + */ + +/** + * \fn bool V4L2Capability::isCapture() + * \brief Identify if the device is capable of capturing video + * \return True if the device can capture video frames + */ + +/** + * \fn bool V4L2Capability::isOutput() + * \brief Identify if the device is capable of outputting video + * \return True if the device can output video frames + */ + +/** + * \fn bool V4L2Capability::hasStreaming() + * \brief Determine if the device can perform Streaming I/O + * \return True if the device provides Streaming I/O IOCTLs + */ + +/** + * \class V4L2Device + * \brief V4L2Device object and API + * + * The V4L2 Device API class models an instance of a V4L2 device node. + * It is constructed with the path to a V4L2 video device node. The device node + * is only opened upon a call to open() which must be checked for success. + * + * The device capabilities are validated when the device is opened and the + * device is rejected if it is not a suitable V4L2 capture or output device, or + * if the device does not support streaming I/O. + * + * No API call other than open(), isOpen() and close() shall be called on an + * unopened device instance. + * + * Upon destruction any device left open will be closed, and any resources + * released. + */ + +/** + * \brief Construct a V4L2Device + * \param devnode The file-system path to the video device node + */ +V4L2Device::V4L2Device(const std::string &devnode) + : devnode_(devnode), fd_(-1) +{ +} + +V4L2Device::~V4L2Device() +{ + close(); +} + +/** + * \brief Open a V4L2 device and query its capabilities + * \return 0 on success, or a negative error code otherwise + */ +int V4L2Device::open() +{ + int ret; + + if (isOpen()) { + LOG(Error) << "Device already open"; + return -EBUSY; + } + + ret = ::open(devnode_.c_str(), O_RDWR); + if (ret < 0) { + ret = -errno; + LOG(Error) << "Failed to open V4L2 device '" << devnode_ + << "': " << strerror(-ret); + return ret; + } + fd_ = ret; + + ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_); + if (ret < 0) { + ret = -errno; + LOG(Error) << "Failed to query device capabilities: " + << strerror(-ret); + return ret; + } + + LOG(Debug) << "Opened '" << devnode_ << "' " + << caps_.bus_info() << ": " << caps_.driver() + << ": " << caps_.card(); + + if (!caps_.isCapture() && !caps_.isOutput()) { + LOG(Debug) << "Device is not a supported type"; + return -EINVAL; + } + + if (!caps_.hasStreaming()) { + LOG(Error) << "Device does not support streaming I/O"; + return -EINVAL; + } + + return 0; +} + +/** + * \brief Check if device is successfully opened + * \return True if the device is open, false otherwise + */ +bool V4L2Device::isOpen() const +{ + return fd_ != -1; +} + +/** + * \brief Close the device, releasing any resources acquired by open() + */ +void V4L2Device::close() +{ + if (fd_ < 0) + return; + + ::close(fd_); + fd_ = -1; +} + +/** + * \fn const char *V4L2Device::driverName() + * \brief Retrieve the name of the V4L2 device driver + * \return The string containing the driver name + */ + +/** + * \fn const char *V4L2Device::deviceName() + * \brief Retrieve the name of the V4L2 device + * \return The string containing the device name + */ + +/** + * \fn const char *V4L2Device::busName() + * \brief Retrieve the location of the device in the system + * \return The string containing the device location + */ + +} /* namespace libcamera */ From patchwork Thu Jan 17 21:27:03 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 264 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 40A4860C99 for ; Thu, 17 Jan 2019 22:27:13 +0100 (CET) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C89B8B6B; Thu, 17 Jan 2019 22:27:12 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1547760432; bh=IMCeMnGyTTQX38rSg2Dop8a0AoZA1vscE7yzFyvGviA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IRoH4kVF70CUUfmJlFY54g0+P8OerSQUYIMy6Xk1PWIwNYxoMJQl2CZVSAN8ljur9 4UIAcwBqP5LNgnmv8XahW28oEV6q2vsNCgwJ6g039vMdfDCTxIOzpjSMQ4/wfGZMwe 0ZkR5aLubPT1F+8naaYx20wAPMJqXde0eQjLjwlY= From: Kieran Bingham To: LibCamera Devel Date: Thu, 17 Jan 2019 21:27:03 +0000 Message-Id: <20190117212703.24047-3-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190117212703.24047-1-kieran.bingham@ideasonboard.com> References: <20190117212703.24047-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 2/2] test: v4l2_device: Add test suite and initial test X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 17 Jan 2019 21:27:13 -0000 Provide a base class to construct a v4l2_device object for further tests and an initial test which validates the FD handle can not be leaked. Reviewed-by: Niklas Söderlund Signed-off-by: Kieran Bingham --- test/meson.build | 1 + test/v4l2_device/double_open.cpp | 38 +++++++++++++++++++++++ test/v4l2_device/meson.build | 12 ++++++++ test/v4l2_device/v4l2_device_test.cpp | 43 +++++++++++++++++++++++++++ test/v4l2_device/v4l2_device_test.h | 27 +++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 test/v4l2_device/double_open.cpp create mode 100644 test/v4l2_device/meson.build create mode 100644 test/v4l2_device/v4l2_device_test.cpp create mode 100644 test/v4l2_device/v4l2_device_test.h diff --git a/test/meson.build b/test/meson.build index 32152888a55e..fb6b115eb2ab 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,6 +1,7 @@ subdir('libtest') subdir('media_device') +subdir('v4l2_device') public_tests = [ ['event', 'event.cpp'], diff --git a/test/v4l2_device/double_open.cpp b/test/v4l2_device/double_open.cpp new file mode 100644 index 000000000000..ca2b201454a4 --- /dev/null +++ b/test/v4l2_device/double_open.cpp @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include + +#include "v4l2_device_test.h" + +namespace { + +class DoubleOpen : public V4L2DeviceTest +{ +protected: + int run() + { + int ret; + + /* + * Expect failure: The device has already been opened by the + * V4L2DeviceTest base class + */ + ret = dev_->open(); + if (!ret) { + std::cout << "Double open erroneously succeeded" << std::endl; + dev_->close(); + return TestFail; + } + + return TestPass; + } +}; + +} /* namespace */ + +TEST_REGISTER(DoubleOpen); diff --git a/test/v4l2_device/meson.build b/test/v4l2_device/meson.build new file mode 100644 index 000000000000..41675a303498 --- /dev/null +++ b/test/v4l2_device/meson.build @@ -0,0 +1,12 @@ +# Tests are listed in order of complexity. +# They are not alphabetically sorted. +v4l2_device_tests = [ + [ 'double_open', 'double_open.cpp' ], +] + +foreach t : v4l2_device_tests + exe = executable(t[0], [t[1], 'v4l2_device_test.cpp'], + link_with : test_libraries, + include_directories : test_includes_internal) + test(t[0], exe, suite: 'v4l2_device', is_parallel: false) +endforeach diff --git a/test/v4l2_device/v4l2_device_test.cpp b/test/v4l2_device/v4l2_device_test.cpp new file mode 100644 index 000000000000..362553712caa --- /dev/null +++ b/test/v4l2_device/v4l2_device_test.cpp @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include +#include + +#include "v4l2_device_test.h" + +using namespace libcamera; + +bool exists(const std::string &path) +{ + struct stat sb; + + if (stat(path.c_str(), &sb) == 0) + return true; + + return false; +} + +int V4L2DeviceTest::init() +{ + const std::string device("/dev/video0"); + + /* Validate the device node exists. */ + if (!exists(device)) { + std::cout << "No video device available" << std::endl; + return TestSkip; + } + + dev_ = new V4L2Device(device); + + return dev_->open(); +} + +void V4L2DeviceTest::cleanup() +{ + delete dev_; +}; diff --git a/test/v4l2_device/v4l2_device_test.h b/test/v4l2_device/v4l2_device_test.h new file mode 100644 index 000000000000..405cb7d6f404 --- /dev/null +++ b/test/v4l2_device/v4l2_device_test.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * vl42device_test.h - libcamera v4l2device test base class + */ +#ifndef __LIBCAMERA_V4L2_DEVICE_TEST_H_ +#define __LIBCAMERA_V4L2_DEVICE_TEST_H_ + +#include "test.h" +#include "v4l2_device.h" + +using namespace libcamera; + +class V4L2DeviceTest : public Test +{ +public: + V4L2DeviceTest() : dev_(nullptr) { }; + +protected: + int init(); + void cleanup(); + + V4L2Device *dev_; +}; + +#endif /* __LIBCAMERA_V4L2_DEVICE_TEST_H_ */