[libcamera-devel,v5,2/4] libcamera: Introduce V4L2Device base class

Message ID 20190619110548.20742-3-jacopo@jmondi.org
State Accepted
Headers show
Series
  • libcamera: Introduce V4L2Device base class
Related show

Commit Message

Jacopo Mondi June 19, 2019, 11:05 a.m. UTC
The V4L2 devices and subdevices share a few common operations,like
opening and closing a device node, and perform IOCTLs on the device.

With the forthcoming introduction of support for V4L2 controls, the
quantity of shared code will increase, as the control support
implementation is identical for the two derived classes.

To maximize code re-use and avoid duplications, provide a V4L2Device
base class which groups the common operations and members.

The newly introduced base class provides methods to open/close a device
node, access the file descriptor, and perform IOCTLs on the device.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 src/libcamera/include/v4l2_device.h      |  43 +++++++
 src/libcamera/include/v4l2_subdevice.h   |   9 +-
 src/libcamera/include/v4l2_videodevice.h |  10 +-
 src/libcamera/meson.build                |   2 +
 src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
 src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
 src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
 7 files changed, 239 insertions(+), 156 deletions(-)
 create mode 100644 src/libcamera/include/v4l2_device.h
 create mode 100644 src/libcamera/v4l2_device.cpp

Comments

Laurent Pinchart June 19, 2019, 12:20 p.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> The V4L2 devices and subdevices share a few common operations,like
> opening and closing a device node, and perform IOCTLs on the device.
> 
> With the forthcoming introduction of support for V4L2 controls, the
> quantity of shared code will increase, as the control support
> implementation is identical for the two derived classes.
> 
> To maximize code re-use and avoid duplications, provide a V4L2Device
> base class which groups the common operations and members.
> 
> The newly introduced base class provides methods to open/close a device
> node, access the file descriptor, and perform IOCTLs on the device.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  src/libcamera/include/v4l2_device.h      |  43 +++++++
>  src/libcamera/include/v4l2_subdevice.h   |   9 +-
>  src/libcamera/include/v4l2_videodevice.h |  10 +-
>  src/libcamera/meson.build                |   2 +
>  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
>  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
>  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
>  7 files changed, 239 insertions(+), 156 deletions(-)
>  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..2c26f3eae2b4
> --- /dev/null
> +++ b/src/libcamera/include/v4l2_device.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> + */
> +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> +#define __LIBCAMERA_V4L2_DEVICE_H__
> +
> +#include <string>
> +
> +#include "log.h"
> +
> +namespace libcamera {
> +
> +class V4L2Device : protected Loggable
> +{
> +public:
> +	void close();
> +	bool isOpen() const { return fd_ != -1; }
> +	const std::string &deviceNode() const { return deviceNode_; }
> +
> +protected:
> +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> +	~V4L2Device() {}
> +
> +	int fd() { return fd_; }
> +
> +	int open(unsigned int flags);
> +
> +	int ioctl(unsigned long request, void *argp);
> +
> +	std::string logPrefix() const { return "'" + logTag_ + "'"; }

Let's try not to inline methods that are more complex than just
returning a member of the class.

I didn't notice in my previous review that V4L2Subdevice was using the
entity name as a prefix. I'm afraid my comment about moving logPrefix()
to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
from Loggable. It will thus have a pure virtual logPrefix() inherited
from Loggable, requiring the derived classes to implement that method,
but allowing LOG() statements in V4L2Device to use the log prefix.

Here's the diff I came up with, it can be squashed with this patch if
you agree on the comments.

diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
index 2c26f3eae2b4..8c83adab4d43 100644
--- a/src/libcamera/include/v4l2_device.h
+++ b/src/libcamera/include/v4l2_device.h
@@ -21,7 +21,7 @@ public:
 	const std::string &deviceNode() const { return deviceNode_; }

 protected:
-	V4L2Device(const std::string &deviceNode, const std::string &logTag);
+	V4L2Device(const std::string &deviceNode);
 	~V4L2Device() {}

 	int fd() { return fd_; }
@@ -30,11 +30,8 @@ protected:

 	int ioctl(unsigned long request, void *argp);

-	std::string logPrefix() const { return "'" + logTag_ + "'"; }
-
 private:
 	std::string deviceNode_;
-	std::string logTag_;
 	int fd_;
 };

diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
index b1da4a87c553..9c077674f997 100644
--- a/src/libcamera/include/v4l2_subdevice.h
+++ b/src/libcamera/include/v4l2_subdevice.h
@@ -52,6 +52,9 @@ public:
 	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
 					     const std::string &entity);

+protected:
+	std::string logPrefix() const;
+
 private:
 	std::vector<unsigned int> enumPadCodes(unsigned int pad);
 	std::vector<SizeRange> enumPadSizes(unsigned int pad,
diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
index e73dec13f847..734b34f1dc53 100644
--- a/src/libcamera/include/v4l2_videodevice.h
+++ b/src/libcamera/include/v4l2_videodevice.h
@@ -147,6 +147,9 @@ public:
 	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
 					       const std::string &entity);

+protected:
+	std::string logPrefix() const;
+
 private:
 	int getFormatMeta(V4L2DeviceFormat *format);
 	int setFormatMeta(V4L2DeviceFormat *format);
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index eeed0a70fb0e..1f113780ba45 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -68,17 +68,15 @@ void V4L2Device::close()
 /**
  * \brief Construct a V4L2Device
  * \param[in] deviceNode The device node filesystem path
- * \param[in] logTag The tag to prefix log messages with. Helpful to identify
- * the device in the log output
  *
  * The V4L2Device constructor is protected and can only be accessed by the
  * V4L2VideoDevice and V4L2Subdevice derived classes.
  *
  * Initialize the file descriptor to -1 and store the \a deviceNode to be used
  * at open() time, and the \a logTag to prefix log messages with.
  */
-V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
-	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
+V4L2Device::V4L2Device(const std::string &deviceNode)
+	: deviceNode_(deviceNode), fd_(-1)
 {
 }

@@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
  */
 int V4L2Device::open(unsigned int flags)
 {
+	LOG(V4L2, Info) << "Opening device";
 	if (isOpen()) {
 		LOG(V4L2, Error) << "Device already open";
 		return -EBUSY;
@@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
 	return 0;
 }

-/**
- * \fn V4L2Device::logPrefix()
- * \brief Retrieve the tag to prefix log messages with
- * \return The tag to prefix log messages with
- */
-
 } /* namespace libcamera */
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index d0e1d717b26c..a188298de34c 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
  * path
  */
 V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
-	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
+	: V4L2Device(entity->deviceNode()), entity_(entity)
 {
 }

@@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
 	return new V4L2Subdevice(mediaEntity);
 }

+std::string V4L2Subdevice::logPrefix() const
+{
+	return "'" + entity_->name() + "'";
+}
+
 std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
 {
 	std::vector<unsigned int> codes;
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 403e0b2e0653..2de3751f467e 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
  * \param[in] deviceNode The file-system path to the video device node
  */
 V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
-	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
+	: V4L2Device(deviceNode), bufferPool_(nullptr),
 	  queuedBuffersCount_(0), fdEvent_(nullptr)
 {
 	/*
@@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
  * \return The string containing the device location
  */

+std::string V4L2VideoDevice::logPrefix() const
+{
+	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
+}
+
 /**
  * \brief Retrieve the image format set on the V4L2 device
  * \param[out] format The image format applied on the device

> +
> +private:
> +	std::string deviceNode_;
> +	std::string logTag_;
> +	int fd_;
> +};
> +
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> index 9afd28b6db02..b1da4a87c553 100644
> --- a/src/libcamera/include/v4l2_subdevice.h
> +++ b/src/libcamera/include/v4l2_subdevice.h
> @@ -16,6 +16,7 @@
>  #include "formats.h"
>  #include "log.h"
>  #include "media_object.h"
> +#include "v4l2_device.h"
>  
>  namespace libcamera {
>  
> @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
>  	const std::string toString() const;
>  };
>  
> -class V4L2Subdevice : protected Loggable
> +class V4L2Subdevice : public V4L2Device
>  {
>  public:
>  	explicit V4L2Subdevice(const MediaEntity *entity);
> @@ -37,8 +38,6 @@ public:
>  	~V4L2Subdevice();
>  
>  	int open();
> -	bool isOpen() const;
> -	void close();
>  
>  	const MediaEntity *entity() const { return entity_; }
>  
> @@ -53,9 +52,6 @@ public:
>  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
>  					     const std::string &entity);
>  
> -protected:
> -	std::string logPrefix() const;
> -
>  private:
>  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
>  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> @@ -65,7 +61,6 @@ private:
>  			 Rectangle *rect);
>  
>  	const MediaEntity *entity_;
> -	int fd_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> index 895658805b47..e73dec13f847 100644
> --- a/src/libcamera/include/v4l2_videodevice.h
> +++ b/src/libcamera/include/v4l2_videodevice.h
> @@ -18,6 +18,7 @@
>  
>  #include "formats.h"
>  #include "log.h"
> +#include "v4l2_device.h"
>  
>  namespace libcamera {
>  
> @@ -112,7 +113,7 @@ public:
>  	const std::string toString() const;
>  };
>  
> -class V4L2VideoDevice : protected Loggable
> +class V4L2VideoDevice : public V4L2Device
>  {
>  public:
>  	explicit V4L2VideoDevice(const std::string &deviceNode);
> @@ -123,13 +124,11 @@ public:
>  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> -	const std::string &deviceNode() const { return deviceNode_; }
>  
>  	int getFormat(V4L2DeviceFormat *format);
>  	int setFormat(V4L2DeviceFormat *format);
> @@ -148,9 +147,6 @@ public:
>  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
>  					       const std::string &entity);
>  
> -protected:
> -	std::string logPrefix() const;
> -
>  private:
>  	int getFormatMeta(V4L2DeviceFormat *format);
>  	int setFormatMeta(V4L2DeviceFormat *format);
> @@ -171,8 +167,6 @@ private:
>  	Buffer *dequeueBuffer();
>  	void bufferAvailable(EventNotifier *notifier);
>  
> -	std::string deviceNode_;
> -	int fd_;
>  	V4L2Capability caps_;
>  
>  	enum v4l2_buf_type bufferType_;
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index 15ab53b1abbe..f26ad5b2dc57 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -23,6 +23,7 @@ libcamera_sources = files([
>      'stream.cpp',
>      'timer.cpp',
>      'utils.cpp',
> +    'v4l2_device.cpp',
>      'v4l2_subdevice.cpp',
>      'v4l2_videodevice.cpp',
>  ])
> @@ -41,6 +42,7 @@ libcamera_headers = files([
>      'include/media_object.h',
>      'include/pipeline_handler.h',
>      'include/utils.h',
> +    'include/v4l2_device.h',
>      'include/v4l2_subdevice.h',
>      'include/v4l2_videodevice.h',
>  ])
> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> new file mode 100644
> index 000000000000..eeed0a70fb0e
> --- /dev/null
> +++ b/src/libcamera/v4l2_device.cpp
> @@ -0,0 +1,144 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> + */
> +
> +#include "v4l2_device.h"
> +
> +#include <fcntl.h>
> +#include <string.h>
> +#include <sys/ioctl.h>
> +#include <unistd.h>
> +
> +#include "log.h"
> +
> +/**
> + * \file v4l2_device.h
> + * \brief Common base for V4L2 devices and subdevices
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(V4L2)
> +
> +/**
> + * \class V4L2Device
> + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> + *
> + * The V4L2Device class groups together the methods and fields common to
> + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> + * class whith methods to open and close the device node associated with the
> + * device and to perform IOCTL system calls on it.
> + *
> + * The V4L2Device class cannot be instantiated directly, as its constructor
> + * is protected. Users should instead create instances of one the derived
> + * classes to model either a V4L2 video device or a V4L2 subdevice.
> + */
> +
> +/**
> + * \brief Close the device node
> + *
> + * Reset the file descriptor to -1
> + */
> +void V4L2Device::close()
> +{
> +	if (!isOpen())
> +		return;
> +
> +	if (::close(fd_) < 0)
> +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> +				 << strerror(errno);
> +	fd_ = -1;
> +}
> +
> +/**
> + * \fn V4L2Device::isOpen()
> + * \brief Check if the V4L2 device node is open
> + * \return True if the V4L2 device node is open, false otherwise
> + */
> +
> +/**
> + * \fn V4L2Device::deviceNode()
> + * \brief Retrieve the device node path
> + * \return The device node path
> + */
> +
> +/**
> + * \brief Construct a V4L2Device
> + * \param[in] deviceNode The device node filesystem path
> + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> + * the device in the log output
> + *
> + * The V4L2Device constructor is protected and can only be accessed by the
> + * V4L2VideoDevice and V4L2Subdevice derived classes.

I would drop this sentence.

> + *
> + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> + * at open() time, and the \a logTag to prefix log messages with.
> + */
> +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> +{
> +}
> +
> +/**
> + * \fn V4L2Device::fd()
> + * \brief Retrieve the V4L2 device file descriptor
> + * \return The V4L2 device file descriptor, -1 if the device node is not open
> + */
> +
> +/**
> + * \brief Open a V4L2 device node
> + * \param[in] flags Access mode flags
> + *
> + * Open the device node path with the provided access mode \a flags and
> + * initialize the file descriptor, which was initially set to -1.
> + *
> + * \return 0 on success or a negative error code otherwise
> + */
> +int V4L2Device::open(unsigned int flags)
> +{
> +	if (isOpen()) {
> +		LOG(V4L2, Error) << "Device already open";
> +		return -EBUSY;
> +	}
> +
> +	int ret = ::open(deviceNode_.c_str(), flags);
> +	if (ret < 0) {
> +		ret = -errno;
> +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> +				 << strerror(-ret);
> +		return ret;
> +	}
> +
> +	fd_ = ret;
> +
> +	return 0;
> +}
> +
> +/**
> + * \brief Perform an IOCTL system call on the device node
> + * \param[in] request The IOCTL request code
> + * \param[in] argp A pointer to the IOCTL argument
> + * \return 0 on success or a negative error code otherwise
> + */
> +int V4L2Device::ioctl(unsigned long request, void *argp)
> +{
> +	/*
> +	 * Printing out an error message is usually better performed
> +	 * in the caller, which can provide more context.
> +	 */
> +	if (::ioctl(fd_, request, argp) < 0)
> +		return -errno;
> +
> +	return 0;
> +}
> +
> +/**
> + * \fn V4L2Device::logPrefix()
> + * \brief Retrieve the tag to prefix log messages with
> + * \return The tag to prefix log messages with
> + */
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> index 48f1fcb30ed4..d0e1d717b26c 100644
> --- a/src/libcamera/v4l2_subdevice.cpp
> +++ b/src/libcamera/v4l2_subdevice.cpp
> @@ -29,7 +29,7 @@
>  
>  namespace libcamera {
>  
> -LOG_DEFINE_CATEGORY(V4L2Subdev)
> +LOG_DECLARE_CATEGORY(V4L2)
>  
>  /**
>   * \struct V4L2SubdeviceFormat
> @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
>   * path
>   */
>  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> -	: entity_(entity), fd_(-1)
> +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
>  {
>  }
>  
> @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
>   */
>  int V4L2Subdevice::open()
>  {
> -	int ret;
> -
> -	if (isOpen()) {
> -		LOG(V4L2Subdev, Error) << "Device already open";
> -		return -EBUSY;
> -	}
> -
> -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> -	if (ret < 0) {
> -		ret = -errno;
> -		LOG(V4L2Subdev, Error)
> -			<< "Failed to open V4L2 subdevice '"
> -			<< entity_->deviceNode() << "': " << strerror(-ret);
> -		return ret;
> -	}
> -	fd_ = ret;
> -
> -	return 0;
> -}
> -
> -/**
> - * \brief Check if the subdevice is open
> - * \return True if the subdevice is open, false otherwise
> - */
> -bool V4L2Subdevice::isOpen() const
> -{
> -	return fd_ != -1;
> -}
> -
> -/**
> - * \brief Close the subdevice, releasing any resources acquired by open()
> - */
> -void V4L2Subdevice::close()
> -{
> -	if (!isOpen())
> -		return;
> -
> -	::close(fd_);
> -	fd_ = -1;
> +	return V4L2Device::open(O_RDWR);
>  }
>  
>  /**
> @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
>  	ImageFormats formats;
>  
>  	if (pad >= entity_->pads().size()) {
> -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> +		LOG(V4L2, Error) << "Invalid pad: " << pad;
>  		return {};
>  	}
>  
> @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
>  			return {};
>  
>  		if (formats.addFormat(code, sizes)) {
> -			LOG(V4L2Subdev, Error)
> +			LOG(V4L2, Error)
>  				<< "Could not add sizes for media bus code "
>  				<< code << " on pad " << pad;
>  			return {};
> @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
>  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
>  	subdevFmt.pad = pad;
>  
> -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
>  	if (ret) {
> -		ret = -errno;
> -		LOG(V4L2Subdev, Error)
> +		LOG(V4L2, Error)
>  			<< "Unable to get format on pad " << pad
>  			<< ": " << strerror(-ret);
>  		return ret;
> @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
>  	subdevFmt.format.height = format->size.height;
>  	subdevFmt.format.code = format->mbus_code;
>  
> -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
>  	if (ret) {
> -		ret = -errno;
> -		LOG(V4L2Subdev, Error)
> +		LOG(V4L2, Error)
>  			<< "Unable to set format on pad " << pad
>  			<< ": " << strerror(-ret);
>  		return ret;
> @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
>  	return new V4L2Subdevice(mediaEntity);
>  }
>  
> -std::string V4L2Subdevice::logPrefix() const
> -{
> -	return "'" + entity_->name() + "'";
> -}
> -
>  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
>  {
>  	std::vector<unsigned int> codes;
> @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
>  		mbusEnum.index = index;
>  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
>  
> -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
>  		if (ret)
>  			break;
>  
>  		codes.push_back(mbusEnum.code);
>  	}
>  
> -	if (ret && errno != EINVAL) {
> -		ret = errno;
> -		LOG(V4L2Subdev, Error)
> +	if (ret < 0 && ret != -EINVAL) {
> +		LOG(V4L2, Error)
>  			<< "Unable to enumerate formats on pad " << pad
> -			<< ": " << strerror(ret);
> +			<< ": " << strerror(-ret);
>  		return {};
>  	}
>  
> @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
>  		sizeEnum.code = code;
>  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
>  
> -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
>  		if (ret)
>  			break;
>  
> @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
>  				   sizeEnum.max_width, sizeEnum.max_height);
>  	}
>  
> -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> -		ret = -errno;
> -		LOG(V4L2Subdev, Error)
> +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> +		LOG(V4L2, Error)
>  			<< "Unable to enumerate sizes on pad " << pad
>  			<< ": " << strerror(-ret);
>  		return {};
> @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
>  	sel.r.width = rect->w;
>  	sel.r.height = rect->h;
>  
> -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
>  	if (ret < 0) {
> -		ret = -errno;
> -		LOG(V4L2Subdev, Error)
> +		LOG(V4L2, Error)
>  			<< "Unable to set rectangle " << target << " on pad "
>  			<< pad << ": " << strerror(-ret);
>  		return ret;
> diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> index 0e70498c8bb1..403e0b2e0653 100644
> --- a/src/libcamera/v4l2_videodevice.cpp
> +++ b/src/libcamera/v4l2_videodevice.cpp
> @@ -30,7 +30,7 @@
>   */
>  namespace libcamera {
>  
> -LOG_DEFINE_CATEGORY(V4L2)
> +LOG_DECLARE_CATEGORY(V4L2)
>  
>  /**
>   * \struct V4L2Capability
> @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
>   * \param[in] deviceNode The file-system path to the video device node
>   */
>  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
>  	  queuedBuffersCount_(0), fdEvent_(nullptr)
>  {
>  	/*
> @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
>  {
>  	int ret;
>  
> -	if (isOpen()) {
> -		LOG(V4L2, Error) << "Device already open";
> -		return -EBUSY;
> -	}
> -
> -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> -	if (ret < 0) {
> -		ret = -errno;
> -		LOG(V4L2, Error)
> -			<< "Failed to open V4L2 device: " << strerror(-ret);
> +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> +	if (ret < 0)
>  		return ret;
> -	}
> -	fd_ = ret;
>  
> -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to query device capabilities: "
>  			<< strerror(-ret);
> @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
>  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
>  	 */
>  	if (caps_.isVideoCapture()) {
> -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
>  		bufferType_ = caps_.isMultiplanar()
>  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
>  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
>  	} else if (caps_.isVideoOutput()) {
> -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
>  		bufferType_ = caps_.isMultiplanar()
>  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
>  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
>  	} else if (caps_.isMetaCapture()) {
> -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
>  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
>  	} else if (caps_.isMetaOutput()) {
> -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
>  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
>  	} else {
>  		LOG(V4L2, Error) << "Device is not a supported type";
> @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
>  	return 0;
>  }
>  
> -/**
> - * \brief Check if device is successfully opened
> - * \return True if the device is open, false otherwise
> - */
> -bool V4L2VideoDevice::isOpen() const
> -{
> -	return fd_ != -1;
> -}
> -
>  /**
>   * \brief Close the device, releasing any resources acquired by open()
>   */
>  void V4L2VideoDevice::close()
>  {
> -	if (fd_ < 0)
> +	if (!isOpen())
>  		return;
>  
>  	releaseBuffers();
>  	delete fdEvent_;
>  
> -	::close(fd_);
> -	fd_ = -1;
> +	V4L2Device::close();
>  }
>  
>  /**
> @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
>   * \return The string containing the device location
>   */
>  
> -/**
> - * \fn V4L2VideoDevice::deviceNode()
> - * \brief Retrieve the video device node path
> - * \return The video device device node path
> - */
> -
> -std::string V4L2VideoDevice::logPrefix() const
> -{
> -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> -}
> -
>  /**
>   * \brief Retrieve the image format set on the V4L2 device
>   * \param[out] format The image format applied on the device
> @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
>  	int ret;
>  
>  	v4l2Format.type = bufferType_;
> -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
>  	v4l2Format.type = bufferType_;
>  	pix->dataformat = format->fourcc;
>  	pix->buffersize = format->planes[0].size;
> -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
>  	int ret;
>  
>  	v4l2Format.type = bufferType_;
> -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
>  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
>  	}
>  
> -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
>  	int ret;
>  
>  	v4l2Format.type = bufferType_;
> -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
>  	pix->pixelformat = format->fourcc;
>  	pix->bytesperline = format->planes[0].bpl;
>  	pix->field = V4L2_FIELD_NONE;
> -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
>  	if (ret) {
> -		ret = -errno;
>  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
>  		return ret;
>  	}
> @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
>  	rb.type = bufferType_;
>  	rb.memory = memoryType_;
>  
> -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> +	ret = ioctl(VIDIOC_REQBUFS, &rb);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Unable to request " << count << " buffers: "
>  			<< strerror(-ret);
> @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
>  		buf.length = VIDEO_MAX_PLANES;
>  		buf.m.planes = planes;
>  
> -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
>  		if (ret < 0) {
> -			ret = -errno;
>  			LOG(V4L2, Error)
>  				<< "Unable to query buffer " << i << ": "
>  				<< strerror(-ret);
> @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
>  	expbuf.plane = planeIndex;
>  	expbuf.flags = O_RDWR;
>  
> -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to export buffer: " << strerror(-ret);
>  		return ret;
> @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
>  		pixelformatEnum.index = index;
>  		pixelformatEnum.type = bufferType_;
>  
> -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
>  		if (ret)
>  			break;
>  
>  		formats.push_back(pixelformatEnum.pixelformat);
>  	}
>  
> -	if (ret && errno != EINVAL) {
> -		ret = -errno;
> +	if (ret && ret != -EINVAL) {
>  		LOG(V4L2, Error)
>  			<< "Unable to enumerate pixel formats: "
>  			<< strerror(-ret);
> @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
>  		frameSize.index = index;
>  		frameSize.pixel_format = pixelFormat;
>  
> -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
>  		if (ret)
>  			break;
>  
> @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
>  		}
>  	}
>  
> -	if (ret && errno != EINVAL) {
> -		ret = -errno;
> +	if (ret && ret != -EINVAL) {
>  		LOG(V4L2, Error)
>  			<< "Unable to enumerate frame sizes: "
>  			<< strerror(-ret);
> @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
>  
>  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
>  
> -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> +	ret = ioctl(VIDIOC_QBUF, &buf);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to queue buffer " << buf.index << ": "
>  			<< strerror(-ret);
> @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
>  		buf.m.planes = planes;
>  	}
>  
> -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> +	ret = ioctl(VIDIOC_DQBUF, &buf);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to dequeue buffer: " << strerror(-ret);
>  		return nullptr;
> @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
>  {
>  	int ret;
>  
> -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to start streaming: " << strerror(-ret);
>  		return ret;
> @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
>  {
>  	int ret;
>  
> -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
>  	if (ret < 0) {
> -		ret = -errno;
>  		LOG(V4L2, Error)
>  			<< "Failed to stop streaming: " << strerror(-ret);
>  		return ret;
Jacopo Mondi June 19, 2019, 12:26 p.m. UTC | #2
Hi Laurent,

On Wed, Jun 19, 2019 at 03:20:29PM +0300, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> > The V4L2 devices and subdevices share a few common operations,like
> > opening and closing a device node, and perform IOCTLs on the device.
> >
> > With the forthcoming introduction of support for V4L2 controls, the
> > quantity of shared code will increase, as the control support
> > implementation is identical for the two derived classes.
> >
> > To maximize code re-use and avoid duplications, provide a V4L2Device
> > base class which groups the common operations and members.
> >
> > The newly introduced base class provides methods to open/close a device
> > node, access the file descriptor, and perform IOCTLs on the device.
> >
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > ---
> >  src/libcamera/include/v4l2_device.h      |  43 +++++++
> >  src/libcamera/include/v4l2_subdevice.h   |   9 +-
> >  src/libcamera/include/v4l2_videodevice.h |  10 +-
> >  src/libcamera/meson.build                |   2 +
> >  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
> >  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
> >  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
> >  7 files changed, 239 insertions(+), 156 deletions(-)
> >  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..2c26f3eae2b4
> > --- /dev/null
> > +++ b/src/libcamera/include/v4l2_device.h
> > @@ -0,0 +1,43 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> > + */
> > +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> > +#define __LIBCAMERA_V4L2_DEVICE_H__
> > +
> > +#include <string>
> > +
> > +#include "log.h"
> > +
> > +namespace libcamera {
> > +
> > +class V4L2Device : protected Loggable
> > +{
> > +public:
> > +	void close();
> > +	bool isOpen() const { return fd_ != -1; }
> > +	const std::string &deviceNode() const { return deviceNode_; }
> > +
> > +protected:
> > +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > +	~V4L2Device() {}
> > +
> > +	int fd() { return fd_; }
> > +
> > +	int open(unsigned int flags);
> > +
> > +	int ioctl(unsigned long request, void *argp);
> > +
> > +	std::string logPrefix() const { return "'" + logTag_ + "'"; }
>
> Let's try not to inline methods that are more complex than just
> returning a member of the class.
>

Ok. I always assumed the compiler knows better, and for simple methods
like this one, implementing it here or in the cpp file it's a matter
of tastes

> I didn't notice in my previous review that V4L2Subdevice was using the
> entity name as a prefix. I'm afraid my comment about moving logPrefix()
> to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
> from Loggable. It will thus have a pure virtual logPrefix() inherited
> from Loggable, requiring the derived classes to implement that method,
> but allowing LOG() statements in V4L2Device to use the log prefix.
>

Why is this (using the entity name as log prefix) bad for you? The
tag is passed to the base class constructor. Might be a bit of a
shortcut maybe, is this your concern ?

> Here's the diff I came up with, it can be squashed with this patch if
> you agree on the comments.
>
> diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
> index 2c26f3eae2b4..8c83adab4d43 100644
> --- a/src/libcamera/include/v4l2_device.h
> +++ b/src/libcamera/include/v4l2_device.h
> @@ -21,7 +21,7 @@ public:
>  	const std::string &deviceNode() const { return deviceNode_; }
>
>  protected:
> -	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> +	V4L2Device(const std::string &deviceNode);
>  	~V4L2Device() {}
>
>  	int fd() { return fd_; }
> @@ -30,11 +30,8 @@ protected:
>
>  	int ioctl(unsigned long request, void *argp);
>
> -	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> -
>  private:
>  	std::string deviceNode_;
> -	std::string logTag_;
>  	int fd_;
>  };
>
> diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> index b1da4a87c553..9c077674f997 100644
> --- a/src/libcamera/include/v4l2_subdevice.h
> +++ b/src/libcamera/include/v4l2_subdevice.h
> @@ -52,6 +52,9 @@ public:
>  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
>  					     const std::string &entity);
>
> +protected:
> +	std::string logPrefix() const;
> +
>  private:
>  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
>  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> index e73dec13f847..734b34f1dc53 100644
> --- a/src/libcamera/include/v4l2_videodevice.h
> +++ b/src/libcamera/include/v4l2_videodevice.h
> @@ -147,6 +147,9 @@ public:
>  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
>  					       const std::string &entity);
>
> +protected:
> +	std::string logPrefix() const;
> +
>  private:
>  	int getFormatMeta(V4L2DeviceFormat *format);
>  	int setFormatMeta(V4L2DeviceFormat *format);
> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> index eeed0a70fb0e..1f113780ba45 100644
> --- a/src/libcamera/v4l2_device.cpp
> +++ b/src/libcamera/v4l2_device.cpp
> @@ -68,17 +68,15 @@ void V4L2Device::close()
>  /**
>   * \brief Construct a V4L2Device
>   * \param[in] deviceNode The device node filesystem path
> - * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> - * the device in the log output
>   *
>   * The V4L2Device constructor is protected and can only be accessed by the
>   * V4L2VideoDevice and V4L2Subdevice derived classes.
>   *
>   * Initialize the file descriptor to -1 and store the \a deviceNode to be used
>   * at open() time, and the \a logTag to prefix log messages with.
>   */
> -V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> -	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> +V4L2Device::V4L2Device(const std::string &deviceNode)
> +	: deviceNode_(deviceNode), fd_(-1)
>  {
>  }
>
> @@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
>   */
>  int V4L2Device::open(unsigned int flags)
>  {
> +	LOG(V4L2, Info) << "Opening device";
>  	if (isOpen()) {
>  		LOG(V4L2, Error) << "Device already open";
>  		return -EBUSY;
> @@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
>  	return 0;
>  }
>
> -/**
> - * \fn V4L2Device::logPrefix()
> - * \brief Retrieve the tag to prefix log messages with
> - * \return The tag to prefix log messages with
> - */
> -
>  } /* namespace libcamera */
> diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> index d0e1d717b26c..a188298de34c 100644
> --- a/src/libcamera/v4l2_subdevice.cpp
> +++ b/src/libcamera/v4l2_subdevice.cpp
> @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
>   * path
>   */
>  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> -	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> +	: V4L2Device(entity->deviceNode()), entity_(entity)
>  {
>  }
>
> @@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
>  	return new V4L2Subdevice(mediaEntity);
>  }
>
> +std::string V4L2Subdevice::logPrefix() const
> +{
> +	return "'" + entity_->name() + "'";
> +}
> +
>  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
>  {
>  	std::vector<unsigned int> codes;
> diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> index 403e0b2e0653..2de3751f467e 100644
> --- a/src/libcamera/v4l2_videodevice.cpp
> +++ b/src/libcamera/v4l2_videodevice.cpp
> @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
>   * \param[in] deviceNode The file-system path to the video device node
>   */
>  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> -	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> +	: V4L2Device(deviceNode), bufferPool_(nullptr),
>  	  queuedBuffersCount_(0), fdEvent_(nullptr)
>  {
>  	/*
> @@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
>   * \return The string containing the device location
>   */
>
> +std::string V4L2VideoDevice::logPrefix() const
> +{
> +	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> +}
> +
>  /**
>   * \brief Retrieve the image format set on the V4L2 device
>   * \param[out] format The image format applied on the device
>
> > +
> > +private:
> > +	std::string deviceNode_;
> > +	std::string logTag_;
> > +	int fd_;
> > +};
> > +
> > +} /* namespace libcamera */
> > +
> > +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > index 9afd28b6db02..b1da4a87c553 100644
> > --- a/src/libcamera/include/v4l2_subdevice.h
> > +++ b/src/libcamera/include/v4l2_subdevice.h
> > @@ -16,6 +16,7 @@
> >  #include "formats.h"
> >  #include "log.h"
> >  #include "media_object.h"
> > +#include "v4l2_device.h"
> >
> >  namespace libcamera {
> >
> > @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
> >  	const std::string toString() const;
> >  };
> >
> > -class V4L2Subdevice : protected Loggable
> > +class V4L2Subdevice : public V4L2Device
> >  {
> >  public:
> >  	explicit V4L2Subdevice(const MediaEntity *entity);
> > @@ -37,8 +38,6 @@ public:
> >  	~V4L2Subdevice();
> >
> >  	int open();
> > -	bool isOpen() const;
> > -	void close();
> >
> >  	const MediaEntity *entity() const { return entity_; }
> >
> > @@ -53,9 +52,6 @@ public:
> >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> >  					     const std::string &entity);
> >
> > -protected:
> > -	std::string logPrefix() const;
> > -
> >  private:
> >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > @@ -65,7 +61,6 @@ private:
> >  			 Rectangle *rect);
> >
> >  	const MediaEntity *entity_;
> > -	int fd_;
> >  };
> >
> >  } /* namespace libcamera */
> > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > index 895658805b47..e73dec13f847 100644
> > --- a/src/libcamera/include/v4l2_videodevice.h
> > +++ b/src/libcamera/include/v4l2_videodevice.h
> > @@ -18,6 +18,7 @@
> >
> >  #include "formats.h"
> >  #include "log.h"
> > +#include "v4l2_device.h"
> >
> >  namespace libcamera {
> >
> > @@ -112,7 +113,7 @@ public:
> >  	const std::string toString() const;
> >  };
> >
> > -class V4L2VideoDevice : protected Loggable
> > +class V4L2VideoDevice : public V4L2Device
> >  {
> >  public:
> >  	explicit V4L2VideoDevice(const std::string &deviceNode);
> > @@ -123,13 +124,11 @@ public:
> >  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> > -	const std::string &deviceNode() const { return deviceNode_; }
> >
> >  	int getFormat(V4L2DeviceFormat *format);
> >  	int setFormat(V4L2DeviceFormat *format);
> > @@ -148,9 +147,6 @@ public:
> >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> >  					       const std::string &entity);
> >
> > -protected:
> > -	std::string logPrefix() const;
> > -
> >  private:
> >  	int getFormatMeta(V4L2DeviceFormat *format);
> >  	int setFormatMeta(V4L2DeviceFormat *format);
> > @@ -171,8 +167,6 @@ private:
> >  	Buffer *dequeueBuffer();
> >  	void bufferAvailable(EventNotifier *notifier);
> >
> > -	std::string deviceNode_;
> > -	int fd_;
> >  	V4L2Capability caps_;
> >
> >  	enum v4l2_buf_type bufferType_;
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > index 15ab53b1abbe..f26ad5b2dc57 100644
> > --- a/src/libcamera/meson.build
> > +++ b/src/libcamera/meson.build
> > @@ -23,6 +23,7 @@ libcamera_sources = files([
> >      'stream.cpp',
> >      'timer.cpp',
> >      'utils.cpp',
> > +    'v4l2_device.cpp',
> >      'v4l2_subdevice.cpp',
> >      'v4l2_videodevice.cpp',
> >  ])
> > @@ -41,6 +42,7 @@ libcamera_headers = files([
> >      'include/media_object.h',
> >      'include/pipeline_handler.h',
> >      'include/utils.h',
> > +    'include/v4l2_device.h',
> >      'include/v4l2_subdevice.h',
> >      'include/v4l2_videodevice.h',
> >  ])
> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > new file mode 100644
> > index 000000000000..eeed0a70fb0e
> > --- /dev/null
> > +++ b/src/libcamera/v4l2_device.cpp
> > @@ -0,0 +1,144 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> > + */
> > +
> > +#include "v4l2_device.h"
> > +
> > +#include <fcntl.h>
> > +#include <string.h>
> > +#include <sys/ioctl.h>
> > +#include <unistd.h>
> > +
> > +#include "log.h"
> > +
> > +/**
> > + * \file v4l2_device.h
> > + * \brief Common base for V4L2 devices and subdevices
> > + */
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(V4L2)
> > +
> > +/**
> > + * \class V4L2Device
> > + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> > + *
> > + * The V4L2Device class groups together the methods and fields common to
> > + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> > + * class whith methods to open and close the device node associated with the
> > + * device and to perform IOCTL system calls on it.
> > + *
> > + * The V4L2Device class cannot be instantiated directly, as its constructor
> > + * is protected. Users should instead create instances of one the derived
> > + * classes to model either a V4L2 video device or a V4L2 subdevice.
> > + */
> > +
> > +/**
> > + * \brief Close the device node
> > + *
> > + * Reset the file descriptor to -1
> > + */
> > +void V4L2Device::close()
> > +{
> > +	if (!isOpen())
> > +		return;
> > +
> > +	if (::close(fd_) < 0)
> > +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> > +				 << strerror(errno);
> > +	fd_ = -1;
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::isOpen()
> > + * \brief Check if the V4L2 device node is open
> > + * \return True if the V4L2 device node is open, false otherwise
> > + */
> > +
> > +/**
> > + * \fn V4L2Device::deviceNode()
> > + * \brief Retrieve the device node path
> > + * \return The device node path
> > + */
> > +
> > +/**
> > + * \brief Construct a V4L2Device
> > + * \param[in] deviceNode The device node filesystem path
> > + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > + * the device in the log output
> > + *
> > + * The V4L2Device constructor is protected and can only be accessed by the
> > + * V4L2VideoDevice and V4L2Subdevice derived classes.
>
> I would drop this sentence.
>
> > + *
> > + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > + * at open() time, and the \a logTag to prefix log messages with.
> > + */
> > +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > +{
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::fd()
> > + * \brief Retrieve the V4L2 device file descriptor
> > + * \return The V4L2 device file descriptor, -1 if the device node is not open
> > + */
> > +
> > +/**
> > + * \brief Open a V4L2 device node
> > + * \param[in] flags Access mode flags
> > + *
> > + * Open the device node path with the provided access mode \a flags and
> > + * initialize the file descriptor, which was initially set to -1.
> > + *
> > + * \return 0 on success or a negative error code otherwise
> > + */
> > +int V4L2Device::open(unsigned int flags)
> > +{
> > +	if (isOpen()) {
> > +		LOG(V4L2, Error) << "Device already open";
> > +		return -EBUSY;
> > +	}
> > +
> > +	int ret = ::open(deviceNode_.c_str(), flags);
> > +	if (ret < 0) {
> > +		ret = -errno;
> > +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> > +				 << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	fd_ = ret;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \brief Perform an IOCTL system call on the device node
> > + * \param[in] request The IOCTL request code
> > + * \param[in] argp A pointer to the IOCTL argument
> > + * \return 0 on success or a negative error code otherwise
> > + */
> > +int V4L2Device::ioctl(unsigned long request, void *argp)
> > +{
> > +	/*
> > +	 * Printing out an error message is usually better performed
> > +	 * in the caller, which can provide more context.
> > +	 */
> > +	if (::ioctl(fd_, request, argp) < 0)
> > +		return -errno;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::logPrefix()
> > + * \brief Retrieve the tag to prefix log messages with
> > + * \return The tag to prefix log messages with
> > + */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > index 48f1fcb30ed4..d0e1d717b26c 100644
> > --- a/src/libcamera/v4l2_subdevice.cpp
> > +++ b/src/libcamera/v4l2_subdevice.cpp
> > @@ -29,7 +29,7 @@
> >
> >  namespace libcamera {
> >
> > -LOG_DEFINE_CATEGORY(V4L2Subdev)
> > +LOG_DECLARE_CATEGORY(V4L2)
> >
> >  /**
> >   * \struct V4L2SubdeviceFormat
> > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> >   * path
> >   */
> >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > -	: entity_(entity), fd_(-1)
> > +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> >  {
> >  }
> >
> > @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
> >   */
> >  int V4L2Subdevice::open()
> >  {
> > -	int ret;
> > -
> > -	if (isOpen()) {
> > -		LOG(V4L2Subdev, Error) << "Device already open";
> > -		return -EBUSY;
> > -	}
> > -
> > -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> > -	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > -			<< "Failed to open V4L2 subdevice '"
> > -			<< entity_->deviceNode() << "': " << strerror(-ret);
> > -		return ret;
> > -	}
> > -	fd_ = ret;
> > -
> > -	return 0;
> > -}
> > -
> > -/**
> > - * \brief Check if the subdevice is open
> > - * \return True if the subdevice is open, false otherwise
> > - */
> > -bool V4L2Subdevice::isOpen() const
> > -{
> > -	return fd_ != -1;
> > -}
> > -
> > -/**
> > - * \brief Close the subdevice, releasing any resources acquired by open()
> > - */
> > -void V4L2Subdevice::close()
> > -{
> > -	if (!isOpen())
> > -		return;
> > -
> > -	::close(fd_);
> > -	fd_ = -1;
> > +	return V4L2Device::open(O_RDWR);
> >  }
> >
> >  /**
> > @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> >  	ImageFormats formats;
> >
> >  	if (pad >= entity_->pads().size()) {
> > -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> > +		LOG(V4L2, Error) << "Invalid pad: " << pad;
> >  		return {};
> >  	}
> >
> > @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> >  			return {};
> >
> >  		if (formats.addFormat(code, sizes)) {
> > -			LOG(V4L2Subdev, Error)
> > +			LOG(V4L2, Error)
> >  				<< "Could not add sizes for media bus code "
> >  				<< code << " on pad " << pad;
> >  			return {};
> > @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> >  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >  	subdevFmt.pad = pad;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> >  	if (ret) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to get format on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return ret;
> > @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> >  	subdevFmt.format.height = format->size.height;
> >  	subdevFmt.format.code = format->mbus_code;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> >  	if (ret) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to set format on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return ret;
> > @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> >  	return new V4L2Subdevice(mediaEntity);
> >  }
> >
> > -std::string V4L2Subdevice::logPrefix() const
> > -{
> > -	return "'" + entity_->name() + "'";
> > -}
> > -
> >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  {
> >  	std::vector<unsigned int> codes;
> > @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  		mbusEnum.index = index;
> >  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >
> > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> >  		if (ret)
> >  			break;
> >
> >  		codes.push_back(mbusEnum.code);
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = errno;
> > -		LOG(V4L2Subdev, Error)
> > +	if (ret < 0 && ret != -EINVAL) {
> > +		LOG(V4L2, Error)
> >  			<< "Unable to enumerate formats on pad " << pad
> > -			<< ": " << strerror(ret);
> > +			<< ": " << strerror(-ret);
> >  		return {};
> >  	}
> >
> > @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> >  		sizeEnum.code = code;
> >  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >
> > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> >  		if (ret)
> >  			break;
> >
> > @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> >  				   sizeEnum.max_width, sizeEnum.max_height);
> >  	}
> >
> > -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> > +		LOG(V4L2, Error)
> >  			<< "Unable to enumerate sizes on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return {};
> > @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
> >  	sel.r.width = rect->w;
> >  	sel.r.height = rect->h;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> > +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
> >  	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to set rectangle " << target << " on pad "
> >  			<< pad << ": " << strerror(-ret);
> >  		return ret;
> > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > index 0e70498c8bb1..403e0b2e0653 100644
> > --- a/src/libcamera/v4l2_videodevice.cpp
> > +++ b/src/libcamera/v4l2_videodevice.cpp
> > @@ -30,7 +30,7 @@
> >   */
> >  namespace libcamera {
> >
> > -LOG_DEFINE_CATEGORY(V4L2)
> > +LOG_DECLARE_CATEGORY(V4L2)
> >
> >  /**
> >   * \struct V4L2Capability
> > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> >   * \param[in] deviceNode The file-system path to the video device node
> >   */
> >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> > +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> >  {
> >  	/*
> > @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
> >  {
> >  	int ret;
> >
> > -	if (isOpen()) {
> > -		LOG(V4L2, Error) << "Device already open";
> > -		return -EBUSY;
> > -	}
> > -
> > -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> > -	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2, Error)
> > -			<< "Failed to open V4L2 device: " << strerror(-ret);
> > +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> > +	if (ret < 0)
> >  		return ret;
> > -	}
> > -	fd_ = ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> > +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to query device capabilities: "
> >  			<< strerror(-ret);
> > @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
> >  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
> >  	 */
> >  	if (caps_.isVideoCapture()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> >  		bufferType_ = caps_.isMultiplanar()
> >  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> >  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> >  	} else if (caps_.isVideoOutput()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> >  		bufferType_ = caps_.isMultiplanar()
> >  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> >  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> >  	} else if (caps_.isMetaCapture()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> >  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
> >  	} else if (caps_.isMetaOutput()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> >  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
> >  	} else {
> >  		LOG(V4L2, Error) << "Device is not a supported type";
> > @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
> >  	return 0;
> >  }
> >
> > -/**
> > - * \brief Check if device is successfully opened
> > - * \return True if the device is open, false otherwise
> > - */
> > -bool V4L2VideoDevice::isOpen() const
> > -{
> > -	return fd_ != -1;
> > -}
> > -
> >  /**
> >   * \brief Close the device, releasing any resources acquired by open()
> >   */
> >  void V4L2VideoDevice::close()
> >  {
> > -	if (fd_ < 0)
> > +	if (!isOpen())
> >  		return;
> >
> >  	releaseBuffers();
> >  	delete fdEvent_;
> >
> > -	::close(fd_);
> > -	fd_ = -1;
> > +	V4L2Device::close();
> >  }
> >
> >  /**
> > @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
> >   * \return The string containing the device location
> >   */
> >
> > -/**
> > - * \fn V4L2VideoDevice::deviceNode()
> > - * \brief Retrieve the video device node path
> > - * \return The video device device node path
> > - */
> > -
> > -std::string V4L2VideoDevice::logPrefix() const
> > -{
> > -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > -}
> > -
> >  /**
> >   * \brief Retrieve the image format set on the V4L2 device
> >   * \param[out] format The image format applied on the device
> > @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
> >  	v4l2Format.type = bufferType_;
> >  	pix->dataformat = format->fourcc;
> >  	pix->buffersize = format->planes[0].size;
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
> >  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
> >  	}
> >
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
> >  	pix->pixelformat = format->fourcc;
> >  	pix->bytesperline = format->planes[0].bpl;
> >  	pix->field = V4L2_FIELD_NONE;
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
> >  	rb.type = bufferType_;
> >  	rb.memory = memoryType_;
> >
> > -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> > +	ret = ioctl(VIDIOC_REQBUFS, &rb);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Unable to request " << count << " buffers: "
> >  			<< strerror(-ret);
> > @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
> >  		buf.length = VIDEO_MAX_PLANES;
> >  		buf.m.planes = planes;
> >
> > -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> > +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
> >  		if (ret < 0) {
> > -			ret = -errno;
> >  			LOG(V4L2, Error)
> >  				<< "Unable to query buffer " << i << ": "
> >  				<< strerror(-ret);
> > @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
> >  	expbuf.plane = planeIndex;
> >  	expbuf.flags = O_RDWR;
> >
> > -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> > +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to export buffer: " << strerror(-ret);
> >  		return ret;
> > @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
> >  		pixelformatEnum.index = index;
> >  		pixelformatEnum.type = bufferType_;
> >
> > -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> > +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
> >  		if (ret)
> >  			break;
> >
> >  		formats.push_back(pixelformatEnum.pixelformat);
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = -errno;
> > +	if (ret && ret != -EINVAL) {
> >  		LOG(V4L2, Error)
> >  			<< "Unable to enumerate pixel formats: "
> >  			<< strerror(-ret);
> > @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> >  		frameSize.index = index;
> >  		frameSize.pixel_format = pixelFormat;
> >
> > -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
> >  		if (ret)
> >  			break;
> >
> > @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> >  		}
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = -errno;
> > +	if (ret && ret != -EINVAL) {
> >  		LOG(V4L2, Error)
> >  			<< "Unable to enumerate frame sizes: "
> >  			<< strerror(-ret);
> > @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
> >
> >  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
> >
> > -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> > +	ret = ioctl(VIDIOC_QBUF, &buf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to queue buffer " << buf.index << ": "
> >  			<< strerror(-ret);
> > @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
> >  		buf.m.planes = planes;
> >  	}
> >
> > -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> > +	ret = ioctl(VIDIOC_DQBUF, &buf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to dequeue buffer: " << strerror(-ret);
> >  		return nullptr;
> > @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
> >  {
> >  	int ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> > +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to start streaming: " << strerror(-ret);
> >  		return ret;
> > @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
> >  {
> >  	int ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> > +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to stop streaming: " << strerror(-ret);
> >  		return ret;
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart June 19, 2019, 12:29 p.m. UTC | #3
Hi Jacopo,

On Wed, Jun 19, 2019 at 02:26:54PM +0200, Jacopo Mondi wrote:
> On Wed, Jun 19, 2019 at 03:20:29PM +0300, Laurent Pinchart wrote:
> > On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> > > The V4L2 devices and subdevices share a few common operations,like
> > > opening and closing a device node, and perform IOCTLs on the device.
> > >
> > > With the forthcoming introduction of support for V4L2 controls, the
> > > quantity of shared code will increase, as the control support
> > > implementation is identical for the two derived classes.
> > >
> > > To maximize code re-use and avoid duplications, provide a V4L2Device
> > > base class which groups the common operations and members.
> > >
> > > The newly introduced base class provides methods to open/close a device
> > > node, access the file descriptor, and perform IOCTLs on the device.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > ---
> > >  src/libcamera/include/v4l2_device.h      |  43 +++++++
> > >  src/libcamera/include/v4l2_subdevice.h   |   9 +-
> > >  src/libcamera/include/v4l2_videodevice.h |  10 +-
> > >  src/libcamera/meson.build                |   2 +
> > >  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
> > >  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
> > >  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
> > >  7 files changed, 239 insertions(+), 156 deletions(-)
> > >  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..2c26f3eae2b4
> > > --- /dev/null
> > > +++ b/src/libcamera/include/v4l2_device.h
> > > @@ -0,0 +1,43 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2019, Google Inc.
> > > + *
> > > + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> > > + */
> > > +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> > > +#define __LIBCAMERA_V4L2_DEVICE_H__
> > > +
> > > +#include <string>
> > > +
> > > +#include "log.h"
> > > +
> > > +namespace libcamera {
> > > +
> > > +class V4L2Device : protected Loggable
> > > +{
> > > +public:
> > > +	void close();
> > > +	bool isOpen() const { return fd_ != -1; }
> > > +	const std::string &deviceNode() const { return deviceNode_; }
> > > +
> > > +protected:
> > > +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > > +	~V4L2Device() {}
> > > +
> > > +	int fd() { return fd_; }
> > > +
> > > +	int open(unsigned int flags);
> > > +
> > > +	int ioctl(unsigned long request, void *argp);
> > > +
> > > +	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> >
> > Let's try not to inline methods that are more complex than just
> > returning a member of the class.
> 
> Ok. I always assumed the compiler knows better, and for simple methods
> like this one, implementing it here or in the cpp file it's a matter
> of tastes
> 
> > I didn't notice in my previous review that V4L2Subdevice was using the
> > entity name as a prefix. I'm afraid my comment about moving logPrefix()
> > to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
> > from Loggable. It will thus have a pure virtual logPrefix() inherited
> > from Loggable, requiring the derived classes to implement that method,
> > but allowing LOG() statements in V4L2Device to use the log prefix.
> 
> Why is this (using the entity name as log prefix) bad for you? The
> tag is passed to the base class constructor. Might be a bit of a
> shortcut maybe, is this your concern ?

My concern is two-fold. First of all, it requires passing a log tag to
the V4L2Device constructor, which isn't very nice. I thought both
derived classes used the device node name as a log tag, but that was not
correct.

Then, V4L2VideoDevice adds a suffix to the device node name to indicate
the queue direction. For mem-to-mem devices this is really useful, as a
single video device node exposes two queues, each of them handled by a
V4L2VideoDevice instance. I don't want to lose this debugging aid.

> > Here's the diff I came up with, it can be squashed with this patch if
> > you agree on the comments.
> >
> > diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
> > index 2c26f3eae2b4..8c83adab4d43 100644
> > --- a/src/libcamera/include/v4l2_device.h
> > +++ b/src/libcamera/include/v4l2_device.h
> > @@ -21,7 +21,7 @@ public:
> >  	const std::string &deviceNode() const { return deviceNode_; }
> >
> >  protected:
> > -	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > +	V4L2Device(const std::string &deviceNode);
> >  	~V4L2Device() {}
> >
> >  	int fd() { return fd_; }
> > @@ -30,11 +30,8 @@ protected:
> >
> >  	int ioctl(unsigned long request, void *argp);
> >
> > -	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> > -
> >  private:
> >  	std::string deviceNode_;
> > -	std::string logTag_;
> >  	int fd_;
> >  };
> >
> > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > index b1da4a87c553..9c077674f997 100644
> > --- a/src/libcamera/include/v4l2_subdevice.h
> > +++ b/src/libcamera/include/v4l2_subdevice.h
> > @@ -52,6 +52,9 @@ public:
> >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> >  					     const std::string &entity);
> >
> > +protected:
> > +	std::string logPrefix() const;
> > +
> >  private:
> >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > index e73dec13f847..734b34f1dc53 100644
> > --- a/src/libcamera/include/v4l2_videodevice.h
> > +++ b/src/libcamera/include/v4l2_videodevice.h
> > @@ -147,6 +147,9 @@ public:
> >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> >  					       const std::string &entity);
> >
> > +protected:
> > +	std::string logPrefix() const;
> > +
> >  private:
> >  	int getFormatMeta(V4L2DeviceFormat *format);
> >  	int setFormatMeta(V4L2DeviceFormat *format);
> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > index eeed0a70fb0e..1f113780ba45 100644
> > --- a/src/libcamera/v4l2_device.cpp
> > +++ b/src/libcamera/v4l2_device.cpp
> > @@ -68,17 +68,15 @@ void V4L2Device::close()
> >  /**
> >   * \brief Construct a V4L2Device
> >   * \param[in] deviceNode The device node filesystem path
> > - * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > - * the device in the log output
> >   *
> >   * The V4L2Device constructor is protected and can only be accessed by the
> >   * V4L2VideoDevice and V4L2Subdevice derived classes.
> >   *
> >   * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> >   * at open() time, and the \a logTag to prefix log messages with.
> >   */
> > -V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > -	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > +V4L2Device::V4L2Device(const std::string &deviceNode)
> > +	: deviceNode_(deviceNode), fd_(-1)
> >  {
> >  }
> >
> > @@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> >   */
> >  int V4L2Device::open(unsigned int flags)
> >  {
> > +	LOG(V4L2, Info) << "Opening device";
> >  	if (isOpen()) {
> >  		LOG(V4L2, Error) << "Device already open";
> >  		return -EBUSY;
> > @@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
> >  	return 0;
> >  }
> >
> > -/**
> > - * \fn V4L2Device::logPrefix()
> > - * \brief Retrieve the tag to prefix log messages with
> > - * \return The tag to prefix log messages with
> > - */
> > -
> >  } /* namespace libcamera */
> > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > index d0e1d717b26c..a188298de34c 100644
> > --- a/src/libcamera/v4l2_subdevice.cpp
> > +++ b/src/libcamera/v4l2_subdevice.cpp
> > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> >   * path
> >   */
> >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > -	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > +	: V4L2Device(entity->deviceNode()), entity_(entity)
> >  {
> >  }
> >
> > @@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> >  	return new V4L2Subdevice(mediaEntity);
> >  }
> >
> > +std::string V4L2Subdevice::logPrefix() const
> > +{
> > +	return "'" + entity_->name() + "'";
> > +}
> > +
> >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  {
> >  	std::vector<unsigned int> codes;
> > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > index 403e0b2e0653..2de3751f467e 100644
> > --- a/src/libcamera/v4l2_videodevice.cpp
> > +++ b/src/libcamera/v4l2_videodevice.cpp
> > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> >   * \param[in] deviceNode The file-system path to the video device node
> >   */
> >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > -	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > +	: V4L2Device(deviceNode), bufferPool_(nullptr),
> >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> >  {
> >  	/*
> > @@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
> >   * \return The string containing the device location
> >   */
> >
> > +std::string V4L2VideoDevice::logPrefix() const
> > +{
> > +	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > +}
> > +
> >  /**
> >   * \brief Retrieve the image format set on the V4L2 device
> >   * \param[out] format The image format applied on the device
> >
> > > +
> > > +private:
> > > +	std::string deviceNode_;
> > > +	std::string logTag_;
> > > +	int fd_;
> > > +};
> > > +
> > > +} /* namespace libcamera */
> > > +
> > > +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> > > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > > index 9afd28b6db02..b1da4a87c553 100644
> > > --- a/src/libcamera/include/v4l2_subdevice.h
> > > +++ b/src/libcamera/include/v4l2_subdevice.h
> > > @@ -16,6 +16,7 @@
> > >  #include "formats.h"
> > >  #include "log.h"
> > >  #include "media_object.h"
> > > +#include "v4l2_device.h"
> > >
> > >  namespace libcamera {
> > >
> > > @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
> > >  	const std::string toString() const;
> > >  };
> > >
> > > -class V4L2Subdevice : protected Loggable
> > > +class V4L2Subdevice : public V4L2Device
> > >  {
> > >  public:
> > >  	explicit V4L2Subdevice(const MediaEntity *entity);
> > > @@ -37,8 +38,6 @@ public:
> > >  	~V4L2Subdevice();
> > >
> > >  	int open();
> > > -	bool isOpen() const;
> > > -	void close();
> > >
> > >  	const MediaEntity *entity() const { return entity_; }
> > >
> > > @@ -53,9 +52,6 @@ public:
> > >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> > >  					     const std::string &entity);
> > >
> > > -protected:
> > > -	std::string logPrefix() const;
> > > -
> > >  private:
> > >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> > >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > > @@ -65,7 +61,6 @@ private:
> > >  			 Rectangle *rect);
> > >
> > >  	const MediaEntity *entity_;
> > > -	int fd_;
> > >  };
> > >
> > >  } /* namespace libcamera */
> > > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > > index 895658805b47..e73dec13f847 100644
> > > --- a/src/libcamera/include/v4l2_videodevice.h
> > > +++ b/src/libcamera/include/v4l2_videodevice.h
> > > @@ -18,6 +18,7 @@
> > >
> > >  #include "formats.h"
> > >  #include "log.h"
> > > +#include "v4l2_device.h"
> > >
> > >  namespace libcamera {
> > >
> > > @@ -112,7 +113,7 @@ public:
> > >  	const std::string toString() const;
> > >  };
> > >
> > > -class V4L2VideoDevice : protected Loggable
> > > +class V4L2VideoDevice : public V4L2Device
> > >  {
> > >  public:
> > >  	explicit V4L2VideoDevice(const std::string &deviceNode);
> > > @@ -123,13 +124,11 @@ public:
> > >  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> > > -	const std::string &deviceNode() const { return deviceNode_; }
> > >
> > >  	int getFormat(V4L2DeviceFormat *format);
> > >  	int setFormat(V4L2DeviceFormat *format);
> > > @@ -148,9 +147,6 @@ public:
> > >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> > >  					       const std::string &entity);
> > >
> > > -protected:
> > > -	std::string logPrefix() const;
> > > -
> > >  private:
> > >  	int getFormatMeta(V4L2DeviceFormat *format);
> > >  	int setFormatMeta(V4L2DeviceFormat *format);
> > > @@ -171,8 +167,6 @@ private:
> > >  	Buffer *dequeueBuffer();
> > >  	void bufferAvailable(EventNotifier *notifier);
> > >
> > > -	std::string deviceNode_;
> > > -	int fd_;
> > >  	V4L2Capability caps_;
> > >
> > >  	enum v4l2_buf_type bufferType_;
> > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > > index 15ab53b1abbe..f26ad5b2dc57 100644
> > > --- a/src/libcamera/meson.build
> > > +++ b/src/libcamera/meson.build
> > > @@ -23,6 +23,7 @@ libcamera_sources = files([
> > >      'stream.cpp',
> > >      'timer.cpp',
> > >      'utils.cpp',
> > > +    'v4l2_device.cpp',
> > >      'v4l2_subdevice.cpp',
> > >      'v4l2_videodevice.cpp',
> > >  ])
> > > @@ -41,6 +42,7 @@ libcamera_headers = files([
> > >      'include/media_object.h',
> > >      'include/pipeline_handler.h',
> > >      'include/utils.h',
> > > +    'include/v4l2_device.h',
> > >      'include/v4l2_subdevice.h',
> > >      'include/v4l2_videodevice.h',
> > >  ])
> > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > new file mode 100644
> > > index 000000000000..eeed0a70fb0e
> > > --- /dev/null
> > > +++ b/src/libcamera/v4l2_device.cpp
> > > @@ -0,0 +1,144 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2019, Google Inc.
> > > + *
> > > + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> > > + */
> > > +
> > > +#include "v4l2_device.h"
> > > +
> > > +#include <fcntl.h>
> > > +#include <string.h>
> > > +#include <sys/ioctl.h>
> > > +#include <unistd.h>
> > > +
> > > +#include "log.h"
> > > +
> > > +/**
> > > + * \file v4l2_device.h
> > > + * \brief Common base for V4L2 devices and subdevices
> > > + */
> > > +
> > > +namespace libcamera {
> > > +
> > > +LOG_DEFINE_CATEGORY(V4L2)
> > > +
> > > +/**
> > > + * \class V4L2Device
> > > + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> > > + *
> > > + * The V4L2Device class groups together the methods and fields common to
> > > + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> > > + * class whith methods to open and close the device node associated with the
> > > + * device and to perform IOCTL system calls on it.
> > > + *
> > > + * The V4L2Device class cannot be instantiated directly, as its constructor
> > > + * is protected. Users should instead create instances of one the derived
> > > + * classes to model either a V4L2 video device or a V4L2 subdevice.
> > > + */
> > > +
> > > +/**
> > > + * \brief Close the device node
> > > + *
> > > + * Reset the file descriptor to -1
> > > + */
> > > +void V4L2Device::close()
> > > +{
> > > +	if (!isOpen())
> > > +		return;
> > > +
> > > +	if (::close(fd_) < 0)
> > > +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> > > +				 << strerror(errno);
> > > +	fd_ = -1;
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::isOpen()
> > > + * \brief Check if the V4L2 device node is open
> > > + * \return True if the V4L2 device node is open, false otherwise
> > > + */
> > > +
> > > +/**
> > > + * \fn V4L2Device::deviceNode()
> > > + * \brief Retrieve the device node path
> > > + * \return The device node path
> > > + */
> > > +
> > > +/**
> > > + * \brief Construct a V4L2Device
> > > + * \param[in] deviceNode The device node filesystem path
> > > + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > > + * the device in the log output
> > > + *
> > > + * The V4L2Device constructor is protected and can only be accessed by the
> > > + * V4L2VideoDevice and V4L2Subdevice derived classes.
> >
> > I would drop this sentence.
> >
> > > + *
> > > + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > > + * at open() time, and the \a logTag to prefix log messages with.
> > > + */
> > > +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > > +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > > +{
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::fd()
> > > + * \brief Retrieve the V4L2 device file descriptor
> > > + * \return The V4L2 device file descriptor, -1 if the device node is not open
> > > + */
> > > +
> > > +/**
> > > + * \brief Open a V4L2 device node
> > > + * \param[in] flags Access mode flags
> > > + *
> > > + * Open the device node path with the provided access mode \a flags and
> > > + * initialize the file descriptor, which was initially set to -1.
> > > + *
> > > + * \return 0 on success or a negative error code otherwise
> > > + */
> > > +int V4L2Device::open(unsigned int flags)
> > > +{
> > > +	if (isOpen()) {
> > > +		LOG(V4L2, Error) << "Device already open";
> > > +		return -EBUSY;
> > > +	}
> > > +
> > > +	int ret = ::open(deviceNode_.c_str(), flags);
> > > +	if (ret < 0) {
> > > +		ret = -errno;
> > > +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> > > +				 << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	fd_ = ret;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \brief Perform an IOCTL system call on the device node
> > > + * \param[in] request The IOCTL request code
> > > + * \param[in] argp A pointer to the IOCTL argument
> > > + * \return 0 on success or a negative error code otherwise
> > > + */
> > > +int V4L2Device::ioctl(unsigned long request, void *argp)
> > > +{
> > > +	/*
> > > +	 * Printing out an error message is usually better performed
> > > +	 * in the caller, which can provide more context.
> > > +	 */
> > > +	if (::ioctl(fd_, request, argp) < 0)
> > > +		return -errno;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::logPrefix()
> > > + * \brief Retrieve the tag to prefix log messages with
> > > + * \return The tag to prefix log messages with
> > > + */
> > > +
> > > +} /* namespace libcamera */
> > > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > > index 48f1fcb30ed4..d0e1d717b26c 100644
> > > --- a/src/libcamera/v4l2_subdevice.cpp
> > > +++ b/src/libcamera/v4l2_subdevice.cpp
> > > @@ -29,7 +29,7 @@
> > >
> > >  namespace libcamera {
> > >
> > > -LOG_DEFINE_CATEGORY(V4L2Subdev)
> > > +LOG_DECLARE_CATEGORY(V4L2)
> > >
> > >  /**
> > >   * \struct V4L2SubdeviceFormat
> > > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> > >   * path
> > >   */
> > >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > > -	: entity_(entity), fd_(-1)
> > > +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > >  {
> > >  }
> > >
> > > @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
> > >   */
> > >  int V4L2Subdevice::open()
> > >  {
> > > -	int ret;
> > > -
> > > -	if (isOpen()) {
> > > -		LOG(V4L2Subdev, Error) << "Device already open";
> > > -		return -EBUSY;
> > > -	}
> > > -
> > > -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> > > -	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > -			<< "Failed to open V4L2 subdevice '"
> > > -			<< entity_->deviceNode() << "': " << strerror(-ret);
> > > -		return ret;
> > > -	}
> > > -	fd_ = ret;
> > > -
> > > -	return 0;
> > > -}
> > > -
> > > -/**
> > > - * \brief Check if the subdevice is open
> > > - * \return True if the subdevice is open, false otherwise
> > > - */
> > > -bool V4L2Subdevice::isOpen() const
> > > -{
> > > -	return fd_ != -1;
> > > -}
> > > -
> > > -/**
> > > - * \brief Close the subdevice, releasing any resources acquired by open()
> > > - */
> > > -void V4L2Subdevice::close()
> > > -{
> > > -	if (!isOpen())
> > > -		return;
> > > -
> > > -	::close(fd_);
> > > -	fd_ = -1;
> > > +	return V4L2Device::open(O_RDWR);
> > >  }
> > >
> > >  /**
> > > @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > >  	ImageFormats formats;
> > >
> > >  	if (pad >= entity_->pads().size()) {
> > > -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> > > +		LOG(V4L2, Error) << "Invalid pad: " << pad;
> > >  		return {};
> > >  	}
> > >
> > > @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > >  			return {};
> > >
> > >  		if (formats.addFormat(code, sizes)) {
> > > -			LOG(V4L2Subdev, Error)
> > > +			LOG(V4L2, Error)
> > >  				<< "Could not add sizes for media bus code "
> > >  				<< code << " on pad " << pad;
> > >  			return {};
> > > @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > >  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >  	subdevFmt.pad = pad;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > >  	if (ret) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to get format on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return ret;
> > > @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > >  	subdevFmt.format.height = format->size.height;
> > >  	subdevFmt.format.code = format->mbus_code;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > >  	if (ret) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to set format on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return ret;
> > > @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> > >  	return new V4L2Subdevice(mediaEntity);
> > >  }
> > >
> > > -std::string V4L2Subdevice::logPrefix() const
> > > -{
> > > -	return "'" + entity_->name() + "'";
> > > -}
> > > -
> > >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > >  {
> > >  	std::vector<unsigned int> codes;
> > > @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > >  		mbusEnum.index = index;
> > >  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > >  		codes.push_back(mbusEnum.code);
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +	if (ret < 0 && ret != -EINVAL) {
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate formats on pad " << pad
> > > -			<< ": " << strerror(ret);
> > > +			<< ": " << strerror(-ret);
> > >  		return {};
> > >  	}
> > >
> > > @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > >  		sizeEnum.code = code;
> > >  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > > @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > >  				   sizeEnum.max_width, sizeEnum.max_height);
> > >  	}
> > >
> > > -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate sizes on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return {};
> > > @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
> > >  	sel.r.width = rect->w;
> > >  	sel.r.height = rect->h;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to set rectangle " << target << " on pad "
> > >  			<< pad << ": " << strerror(-ret);
> > >  		return ret;
> > > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > > index 0e70498c8bb1..403e0b2e0653 100644
> > > --- a/src/libcamera/v4l2_videodevice.cpp
> > > +++ b/src/libcamera/v4l2_videodevice.cpp
> > > @@ -30,7 +30,7 @@
> > >   */
> > >  namespace libcamera {
> > >
> > > -LOG_DEFINE_CATEGORY(V4L2)
> > > +LOG_DECLARE_CATEGORY(V4L2)
> > >
> > >  /**
> > >   * \struct V4L2Capability
> > > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> > >   * \param[in] deviceNode The file-system path to the video device node
> > >   */
> > >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > > -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> > > +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> > >  {
> > >  	/*
> > > @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
> > >  {
> > >  	int ret;
> > >
> > > -	if (isOpen()) {
> > > -		LOG(V4L2, Error) << "Device already open";
> > > -		return -EBUSY;
> > > -	}
> > > -
> > > -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> > > -	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2, Error)
> > > -			<< "Failed to open V4L2 device: " << strerror(-ret);
> > > +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> > > +	if (ret < 0)
> > >  		return ret;
> > > -	}
> > > -	fd_ = ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> > > +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to query device capabilities: "
> > >  			<< strerror(-ret);
> > > @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
> > >  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
> > >  	 */
> > >  	if (caps_.isVideoCapture()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > >  		bufferType_ = caps_.isMultiplanar()
> > >  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> > >  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > >  	} else if (caps_.isVideoOutput()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > >  		bufferType_ = caps_.isMultiplanar()
> > >  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> > >  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> > >  	} else if (caps_.isMetaCapture()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > >  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
> > >  	} else if (caps_.isMetaOutput()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > >  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
> > >  	} else {
> > >  		LOG(V4L2, Error) << "Device is not a supported type";
> > > @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
> > >  	return 0;
> > >  }
> > >
> > > -/**
> > > - * \brief Check if device is successfully opened
> > > - * \return True if the device is open, false otherwise
> > > - */
> > > -bool V4L2VideoDevice::isOpen() const
> > > -{
> > > -	return fd_ != -1;
> > > -}
> > > -
> > >  /**
> > >   * \brief Close the device, releasing any resources acquired by open()
> > >   */
> > >  void V4L2VideoDevice::close()
> > >  {
> > > -	if (fd_ < 0)
> > > +	if (!isOpen())
> > >  		return;
> > >
> > >  	releaseBuffers();
> > >  	delete fdEvent_;
> > >
> > > -	::close(fd_);
> > > -	fd_ = -1;
> > > +	V4L2Device::close();
> > >  }
> > >
> > >  /**
> > > @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
> > >   * \return The string containing the device location
> > >   */
> > >
> > > -/**
> > > - * \fn V4L2VideoDevice::deviceNode()
> > > - * \brief Retrieve the video device node path
> > > - * \return The video device device node path
> > > - */
> > > -
> > > -std::string V4L2VideoDevice::logPrefix() const
> > > -{
> > > -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > > -}
> > > -
> > >  /**
> > >   * \brief Retrieve the image format set on the V4L2 device
> > >   * \param[out] format The image format applied on the device
> > > @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
> > >  	v4l2Format.type = bufferType_;
> > >  	pix->dataformat = format->fourcc;
> > >  	pix->buffersize = format->planes[0].size;
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
> > >  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
> > >  	}
> > >
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
> > >  	pix->pixelformat = format->fourcc;
> > >  	pix->bytesperline = format->planes[0].bpl;
> > >  	pix->field = V4L2_FIELD_NONE;
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
> > >  	rb.type = bufferType_;
> > >  	rb.memory = memoryType_;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> > > +	ret = ioctl(VIDIOC_REQBUFS, &rb);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to request " << count << " buffers: "
> > >  			<< strerror(-ret);
> > > @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
> > >  		buf.length = VIDEO_MAX_PLANES;
> > >  		buf.m.planes = planes;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> > > +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
> > >  		if (ret < 0) {
> > > -			ret = -errno;
> > >  			LOG(V4L2, Error)
> > >  				<< "Unable to query buffer " << i << ": "
> > >  				<< strerror(-ret);
> > > @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
> > >  	expbuf.plane = planeIndex;
> > >  	expbuf.flags = O_RDWR;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> > > +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to export buffer: " << strerror(-ret);
> > >  		return ret;
> > > @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
> > >  		pixelformatEnum.index = index;
> > >  		pixelformatEnum.type = bufferType_;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> > > +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > >  		formats.push_back(pixelformatEnum.pixelformat);
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = -errno;
> > > +	if (ret && ret != -EINVAL) {
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate pixel formats: "
> > >  			<< strerror(-ret);
> > > @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > >  		frameSize.index = index;
> > >  		frameSize.pixel_format = pixelFormat;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > > +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > >  		if (ret)
> > >  			break;
> > >
> > > @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > >  		}
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = -errno;
> > > +	if (ret && ret != -EINVAL) {
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate frame sizes: "
> > >  			<< strerror(-ret);
> > > @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
> > >
> > >  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> > > +	ret = ioctl(VIDIOC_QBUF, &buf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to queue buffer " << buf.index << ": "
> > >  			<< strerror(-ret);
> > > @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
> > >  		buf.m.planes = planes;
> > >  	}
> > >
> > > -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> > > +	ret = ioctl(VIDIOC_DQBUF, &buf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to dequeue buffer: " << strerror(-ret);
> > >  		return nullptr;
> > > @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
> > >  {
> > >  	int ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> > > +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to start streaming: " << strerror(-ret);
> > >  		return ret;
> > > @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
> > >  {
> > >  	int ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> > > +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to stop streaming: " << strerror(-ret);
> > >  		return ret;
> >
> > --
> > Regards,
> >
> > Laurent Pinchart
Jacopo Mondi June 19, 2019, 12:38 p.m. UTC | #4
HI Laurent,

On Wed, Jun 19, 2019 at 03:29:08PM +0300, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Wed, Jun 19, 2019 at 02:26:54PM +0200, Jacopo Mondi wrote:
> > On Wed, Jun 19, 2019 at 03:20:29PM +0300, Laurent Pinchart wrote:
> > > On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> > > > The V4L2 devices and subdevices share a few common operations,like
> > > > opening and closing a device node, and perform IOCTLs on the device.
> > > >
> > > > With the forthcoming introduction of support for V4L2 controls, the
> > > > quantity of shared code will increase, as the control support
> > > > implementation is identical for the two derived classes.
> > > >
> > > > To maximize code re-use and avoid duplications, provide a V4L2Device
> > > > base class which groups the common operations and members.
> > > >
> > > > The newly introduced base class provides methods to open/close a device
> > > > node, access the file descriptor, and perform IOCTLs on the device.
> > > >
> > > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > > ---
> > > >  src/libcamera/include/v4l2_device.h      |  43 +++++++
> > > >  src/libcamera/include/v4l2_subdevice.h   |   9 +-
> > > >  src/libcamera/include/v4l2_videodevice.h |  10 +-
> > > >  src/libcamera/meson.build                |   2 +
> > > >  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
> > > >  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
> > > >  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
> > > >  7 files changed, 239 insertions(+), 156 deletions(-)
> > > >  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..2c26f3eae2b4
> > > > --- /dev/null
> > > > +++ b/src/libcamera/include/v4l2_device.h
> > > > @@ -0,0 +1,43 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2019, Google Inc.
> > > > + *
> > > > + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> > > > + */
> > > > +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> > > > +#define __LIBCAMERA_V4L2_DEVICE_H__
> > > > +
> > > > +#include <string>
> > > > +
> > > > +#include "log.h"
> > > > +
> > > > +namespace libcamera {
> > > > +
> > > > +class V4L2Device : protected Loggable
> > > > +{
> > > > +public:
> > > > +	void close();
> > > > +	bool isOpen() const { return fd_ != -1; }
> > > > +	const std::string &deviceNode() const { return deviceNode_; }
> > > > +
> > > > +protected:
> > > > +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > > > +	~V4L2Device() {}
> > > > +
> > > > +	int fd() { return fd_; }
> > > > +
> > > > +	int open(unsigned int flags);
> > > > +
> > > > +	int ioctl(unsigned long request, void *argp);
> > > > +
> > > > +	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> > >
> > > Let's try not to inline methods that are more complex than just
> > > returning a member of the class.
> >
> > Ok. I always assumed the compiler knows better, and for simple methods
> > like this one, implementing it here or in the cpp file it's a matter
> > of tastes
> >
> > > I didn't notice in my previous review that V4L2Subdevice was using the
> > > entity name as a prefix. I'm afraid my comment about moving logPrefix()
> > > to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
> > > from Loggable. It will thus have a pure virtual logPrefix() inherited
> > > from Loggable, requiring the derived classes to implement that method,
> > > but allowing LOG() statements in V4L2Device to use the log prefix.
> >
> > Why is this (using the entity name as log prefix) bad for you? The
> > tag is passed to the base class constructor. Might be a bit of a
> > shortcut maybe, is this your concern ?
>
> My concern is two-fold. First of all, it requires passing a log tag to
> the V4L2Device constructor, which isn't very nice. I thought both
> derived classes used the device node name as a log tag, but that was not
> correct.

I agree it's a bit of a shortcut, yes

>
> Then, V4L2VideoDevice adds a suffix to the device node name to indicate
> the queue direction. For mem-to-mem devices this is really useful, as a
> single video device node exposes two queues, each of them handled by a
> V4L2VideoDevice instance. I don't want to lose this debugging aid.
>

That I considered not that relevant, but I ignore the case of devices
with multiple queues

I'll drop that part, I'll squash your patch on top.

Thanks
   j

> > > Here's the diff I came up with, it can be squashed with this patch if
> > > you agree on the comments.
> > >
> > > diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
> > > index 2c26f3eae2b4..8c83adab4d43 100644
> > > --- a/src/libcamera/include/v4l2_device.h
> > > +++ b/src/libcamera/include/v4l2_device.h
> > > @@ -21,7 +21,7 @@ public:
> > >  	const std::string &deviceNode() const { return deviceNode_; }
> > >
> > >  protected:
> > > -	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > > +	V4L2Device(const std::string &deviceNode);
> > >  	~V4L2Device() {}
> > >
> > >  	int fd() { return fd_; }
> > > @@ -30,11 +30,8 @@ protected:
> > >
> > >  	int ioctl(unsigned long request, void *argp);
> > >
> > > -	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> > > -
> > >  private:
> > >  	std::string deviceNode_;
> > > -	std::string logTag_;
> > >  	int fd_;
> > >  };
> > >
> > > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > > index b1da4a87c553..9c077674f997 100644
> > > --- a/src/libcamera/include/v4l2_subdevice.h
> > > +++ b/src/libcamera/include/v4l2_subdevice.h
> > > @@ -52,6 +52,9 @@ public:
> > >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> > >  					     const std::string &entity);
> > >
> > > +protected:
> > > +	std::string logPrefix() const;
> > > +
> > >  private:
> > >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> > >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > > index e73dec13f847..734b34f1dc53 100644
> > > --- a/src/libcamera/include/v4l2_videodevice.h
> > > +++ b/src/libcamera/include/v4l2_videodevice.h
> > > @@ -147,6 +147,9 @@ public:
> > >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> > >  					       const std::string &entity);
> > >
> > > +protected:
> > > +	std::string logPrefix() const;
> > > +
> > >  private:
> > >  	int getFormatMeta(V4L2DeviceFormat *format);
> > >  	int setFormatMeta(V4L2DeviceFormat *format);
> > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > index eeed0a70fb0e..1f113780ba45 100644
> > > --- a/src/libcamera/v4l2_device.cpp
> > > +++ b/src/libcamera/v4l2_device.cpp
> > > @@ -68,17 +68,15 @@ void V4L2Device::close()
> > >  /**
> > >   * \brief Construct a V4L2Device
> > >   * \param[in] deviceNode The device node filesystem path
> > > - * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > > - * the device in the log output
> > >   *
> > >   * The V4L2Device constructor is protected and can only be accessed by the
> > >   * V4L2VideoDevice and V4L2Subdevice derived classes.
> > >   *
> > >   * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > >   * at open() time, and the \a logTag to prefix log messages with.
> > >   */
> > > -V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > > -	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > > +V4L2Device::V4L2Device(const std::string &deviceNode)
> > > +	: deviceNode_(deviceNode), fd_(-1)
> > >  {
> > >  }
> > >
> > > @@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > >   */
> > >  int V4L2Device::open(unsigned int flags)
> > >  {
> > > +	LOG(V4L2, Info) << "Opening device";
> > >  	if (isOpen()) {
> > >  		LOG(V4L2, Error) << "Device already open";
> > >  		return -EBUSY;
> > > @@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
> > >  	return 0;
> > >  }
> > >
> > > -/**
> > > - * \fn V4L2Device::logPrefix()
> > > - * \brief Retrieve the tag to prefix log messages with
> > > - * \return The tag to prefix log messages with
> > > - */
> > > -
> > >  } /* namespace libcamera */
> > > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > > index d0e1d717b26c..a188298de34c 100644
> > > --- a/src/libcamera/v4l2_subdevice.cpp
> > > +++ b/src/libcamera/v4l2_subdevice.cpp
> > > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> > >   * path
> > >   */
> > >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > > -	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > > +	: V4L2Device(entity->deviceNode()), entity_(entity)
> > >  {
> > >  }
> > >
> > > @@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> > >  	return new V4L2Subdevice(mediaEntity);
> > >  }
> > >
> > > +std::string V4L2Subdevice::logPrefix() const
> > > +{
> > > +	return "'" + entity_->name() + "'";
> > > +}
> > > +
> > >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > >  {
> > >  	std::vector<unsigned int> codes;
> > > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > > index 403e0b2e0653..2de3751f467e 100644
> > > --- a/src/libcamera/v4l2_videodevice.cpp
> > > +++ b/src/libcamera/v4l2_videodevice.cpp
> > > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> > >   * \param[in] deviceNode The file-system path to the video device node
> > >   */
> > >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > > -	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > > +	: V4L2Device(deviceNode), bufferPool_(nullptr),
> > >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> > >  {
> > >  	/*
> > > @@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
> > >   * \return The string containing the device location
> > >   */
> > >
> > > +std::string V4L2VideoDevice::logPrefix() const
> > > +{
> > > +	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > > +}
> > > +
> > >  /**
> > >   * \brief Retrieve the image format set on the V4L2 device
> > >   * \param[out] format The image format applied on the device
> > >
> > > > +
> > > > +private:
> > > > +	std::string deviceNode_;
> > > > +	std::string logTag_;
> > > > +	int fd_;
> > > > +};
> > > > +
> > > > +} /* namespace libcamera */
> > > > +
> > > > +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> > > > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > > > index 9afd28b6db02..b1da4a87c553 100644
> > > > --- a/src/libcamera/include/v4l2_subdevice.h
> > > > +++ b/src/libcamera/include/v4l2_subdevice.h
> > > > @@ -16,6 +16,7 @@
> > > >  #include "formats.h"
> > > >  #include "log.h"
> > > >  #include "media_object.h"
> > > > +#include "v4l2_device.h"
> > > >
> > > >  namespace libcamera {
> > > >
> > > > @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
> > > >  	const std::string toString() const;
> > > >  };
> > > >
> > > > -class V4L2Subdevice : protected Loggable
> > > > +class V4L2Subdevice : public V4L2Device
> > > >  {
> > > >  public:
> > > >  	explicit V4L2Subdevice(const MediaEntity *entity);
> > > > @@ -37,8 +38,6 @@ public:
> > > >  	~V4L2Subdevice();
> > > >
> > > >  	int open();
> > > > -	bool isOpen() const;
> > > > -	void close();
> > > >
> > > >  	const MediaEntity *entity() const { return entity_; }
> > > >
> > > > @@ -53,9 +52,6 @@ public:
> > > >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> > > >  					     const std::string &entity);
> > > >
> > > > -protected:
> > > > -	std::string logPrefix() const;
> > > > -
> > > >  private:
> > > >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> > > >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > > > @@ -65,7 +61,6 @@ private:
> > > >  			 Rectangle *rect);
> > > >
> > > >  	const MediaEntity *entity_;
> > > > -	int fd_;
> > > >  };
> > > >
> > > >  } /* namespace libcamera */
> > > > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > > > index 895658805b47..e73dec13f847 100644
> > > > --- a/src/libcamera/include/v4l2_videodevice.h
> > > > +++ b/src/libcamera/include/v4l2_videodevice.h
> > > > @@ -18,6 +18,7 @@
> > > >
> > > >  #include "formats.h"
> > > >  #include "log.h"
> > > > +#include "v4l2_device.h"
> > > >
> > > >  namespace libcamera {
> > > >
> > > > @@ -112,7 +113,7 @@ public:
> > > >  	const std::string toString() const;
> > > >  };
> > > >
> > > > -class V4L2VideoDevice : protected Loggable
> > > > +class V4L2VideoDevice : public V4L2Device
> > > >  {
> > > >  public:
> > > >  	explicit V4L2VideoDevice(const std::string &deviceNode);
> > > > @@ -123,13 +124,11 @@ public:
> > > >  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> > > > -	const std::string &deviceNode() const { return deviceNode_; }
> > > >
> > > >  	int getFormat(V4L2DeviceFormat *format);
> > > >  	int setFormat(V4L2DeviceFormat *format);
> > > > @@ -148,9 +147,6 @@ public:
> > > >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> > > >  					       const std::string &entity);
> > > >
> > > > -protected:
> > > > -	std::string logPrefix() const;
> > > > -
> > > >  private:
> > > >  	int getFormatMeta(V4L2DeviceFormat *format);
> > > >  	int setFormatMeta(V4L2DeviceFormat *format);
> > > > @@ -171,8 +167,6 @@ private:
> > > >  	Buffer *dequeueBuffer();
> > > >  	void bufferAvailable(EventNotifier *notifier);
> > > >
> > > > -	std::string deviceNode_;
> > > > -	int fd_;
> > > >  	V4L2Capability caps_;
> > > >
> > > >  	enum v4l2_buf_type bufferType_;
> > > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > > > index 15ab53b1abbe..f26ad5b2dc57 100644
> > > > --- a/src/libcamera/meson.build
> > > > +++ b/src/libcamera/meson.build
> > > > @@ -23,6 +23,7 @@ libcamera_sources = files([
> > > >      'stream.cpp',
> > > >      'timer.cpp',
> > > >      'utils.cpp',
> > > > +    'v4l2_device.cpp',
> > > >      'v4l2_subdevice.cpp',
> > > >      'v4l2_videodevice.cpp',
> > > >  ])
> > > > @@ -41,6 +42,7 @@ libcamera_headers = files([
> > > >      'include/media_object.h',
> > > >      'include/pipeline_handler.h',
> > > >      'include/utils.h',
> > > > +    'include/v4l2_device.h',
> > > >      'include/v4l2_subdevice.h',
> > > >      'include/v4l2_videodevice.h',
> > > >  ])
> > > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > > new file mode 100644
> > > > index 000000000000..eeed0a70fb0e
> > > > --- /dev/null
> > > > +++ b/src/libcamera/v4l2_device.cpp
> > > > @@ -0,0 +1,144 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2019, Google Inc.
> > > > + *
> > > > + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> > > > + */
> > > > +
> > > > +#include "v4l2_device.h"
> > > > +
> > > > +#include <fcntl.h>
> > > > +#include <string.h>
> > > > +#include <sys/ioctl.h>
> > > > +#include <unistd.h>
> > > > +
> > > > +#include "log.h"
> > > > +
> > > > +/**
> > > > + * \file v4l2_device.h
> > > > + * \brief Common base for V4L2 devices and subdevices
> > > > + */
> > > > +
> > > > +namespace libcamera {
> > > > +
> > > > +LOG_DEFINE_CATEGORY(V4L2)
> > > > +
> > > > +/**
> > > > + * \class V4L2Device
> > > > + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> > > > + *
> > > > + * The V4L2Device class groups together the methods and fields common to
> > > > + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> > > > + * class whith methods to open and close the device node associated with the
> > > > + * device and to perform IOCTL system calls on it.
> > > > + *
> > > > + * The V4L2Device class cannot be instantiated directly, as its constructor
> > > > + * is protected. Users should instead create instances of one the derived
> > > > + * classes to model either a V4L2 video device or a V4L2 subdevice.
> > > > + */
> > > > +
> > > > +/**
> > > > + * \brief Close the device node
> > > > + *
> > > > + * Reset the file descriptor to -1
> > > > + */
> > > > +void V4L2Device::close()
> > > > +{
> > > > +	if (!isOpen())
> > > > +		return;
> > > > +
> > > > +	if (::close(fd_) < 0)
> > > > +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> > > > +				 << strerror(errno);
> > > > +	fd_ = -1;
> > > > +}
> > > > +
> > > > +/**
> > > > + * \fn V4L2Device::isOpen()
> > > > + * \brief Check if the V4L2 device node is open
> > > > + * \return True if the V4L2 device node is open, false otherwise
> > > > + */
> > > > +
> > > > +/**
> > > > + * \fn V4L2Device::deviceNode()
> > > > + * \brief Retrieve the device node path
> > > > + * \return The device node path
> > > > + */
> > > > +
> > > > +/**
> > > > + * \brief Construct a V4L2Device
> > > > + * \param[in] deviceNode The device node filesystem path
> > > > + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > > > + * the device in the log output
> > > > + *
> > > > + * The V4L2Device constructor is protected and can only be accessed by the
> > > > + * V4L2VideoDevice and V4L2Subdevice derived classes.
> > >
> > > I would drop this sentence.
> > >
> > > > + *
> > > > + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > > > + * at open() time, and the \a logTag to prefix log messages with.
> > > > + */
> > > > +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > > > +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > > > +{
> > > > +}
> > > > +
> > > > +/**
> > > > + * \fn V4L2Device::fd()
> > > > + * \brief Retrieve the V4L2 device file descriptor
> > > > + * \return The V4L2 device file descriptor, -1 if the device node is not open
> > > > + */
> > > > +
> > > > +/**
> > > > + * \brief Open a V4L2 device node
> > > > + * \param[in] flags Access mode flags
> > > > + *
> > > > + * Open the device node path with the provided access mode \a flags and
> > > > + * initialize the file descriptor, which was initially set to -1.
> > > > + *
> > > > + * \return 0 on success or a negative error code otherwise
> > > > + */
> > > > +int V4L2Device::open(unsigned int flags)
> > > > +{
> > > > +	if (isOpen()) {
> > > > +		LOG(V4L2, Error) << "Device already open";
> > > > +		return -EBUSY;
> > > > +	}
> > > > +
> > > > +	int ret = ::open(deviceNode_.c_str(), flags);
> > > > +	if (ret < 0) {
> > > > +		ret = -errno;
> > > > +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> > > > +				 << strerror(-ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	fd_ = ret;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +/**
> > > > + * \brief Perform an IOCTL system call on the device node
> > > > + * \param[in] request The IOCTL request code
> > > > + * \param[in] argp A pointer to the IOCTL argument
> > > > + * \return 0 on success or a negative error code otherwise
> > > > + */
> > > > +int V4L2Device::ioctl(unsigned long request, void *argp)
> > > > +{
> > > > +	/*
> > > > +	 * Printing out an error message is usually better performed
> > > > +	 * in the caller, which can provide more context.
> > > > +	 */
> > > > +	if (::ioctl(fd_, request, argp) < 0)
> > > > +		return -errno;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +/**
> > > > + * \fn V4L2Device::logPrefix()
> > > > + * \brief Retrieve the tag to prefix log messages with
> > > > + * \return The tag to prefix log messages with
> > > > + */
> > > > +
> > > > +} /* namespace libcamera */
> > > > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > > > index 48f1fcb30ed4..d0e1d717b26c 100644
> > > > --- a/src/libcamera/v4l2_subdevice.cpp
> > > > +++ b/src/libcamera/v4l2_subdevice.cpp
> > > > @@ -29,7 +29,7 @@
> > > >
> > > >  namespace libcamera {
> > > >
> > > > -LOG_DEFINE_CATEGORY(V4L2Subdev)
> > > > +LOG_DECLARE_CATEGORY(V4L2)
> > > >
> > > >  /**
> > > >   * \struct V4L2SubdeviceFormat
> > > > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> > > >   * path
> > > >   */
> > > >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > > > -	: entity_(entity), fd_(-1)
> > > > +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > > >  {
> > > >  }
> > > >
> > > > @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
> > > >   */
> > > >  int V4L2Subdevice::open()
> > > >  {
> > > > -	int ret;
> > > > -
> > > > -	if (isOpen()) {
> > > > -		LOG(V4L2Subdev, Error) << "Device already open";
> > > > -		return -EBUSY;
> > > > -	}
> > > > -
> > > > -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> > > > -	if (ret < 0) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > -			<< "Failed to open V4L2 subdevice '"
> > > > -			<< entity_->deviceNode() << "': " << strerror(-ret);
> > > > -		return ret;
> > > > -	}
> > > > -	fd_ = ret;
> > > > -
> > > > -	return 0;
> > > > -}
> > > > -
> > > > -/**
> > > > - * \brief Check if the subdevice is open
> > > > - * \return True if the subdevice is open, false otherwise
> > > > - */
> > > > -bool V4L2Subdevice::isOpen() const
> > > > -{
> > > > -	return fd_ != -1;
> > > > -}
> > > > -
> > > > -/**
> > > > - * \brief Close the subdevice, releasing any resources acquired by open()
> > > > - */
> > > > -void V4L2Subdevice::close()
> > > > -{
> > > > -	if (!isOpen())
> > > > -		return;
> > > > -
> > > > -	::close(fd_);
> > > > -	fd_ = -1;
> > > > +	return V4L2Device::open(O_RDWR);
> > > >  }
> > > >
> > > >  /**
> > > > @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > > >  	ImageFormats formats;
> > > >
> > > >  	if (pad >= entity_->pads().size()) {
> > > > -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> > > > +		LOG(V4L2, Error) << "Invalid pad: " << pad;
> > > >  		return {};
> > > >  	}
> > > >
> > > > @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > > >  			return {};
> > > >
> > > >  		if (formats.addFormat(code, sizes)) {
> > > > -			LOG(V4L2Subdev, Error)
> > > > +			LOG(V4L2, Error)
> > > >  				<< "Could not add sizes for media bus code "
> > > >  				<< code << " on pad " << pad;
> > > >  			return {};
> > > > @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > > >  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > >  	subdevFmt.pad = pad;
> > > >
> > > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > > > +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > +		LOG(V4L2, Error)
> > > >  			<< "Unable to get format on pad " << pad
> > > >  			<< ": " << strerror(-ret);
> > > >  		return ret;
> > > > @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > > >  	subdevFmt.format.height = format->size.height;
> > > >  	subdevFmt.format.code = format->mbus_code;
> > > >
> > > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > > > +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > +		LOG(V4L2, Error)
> > > >  			<< "Unable to set format on pad " << pad
> > > >  			<< ": " << strerror(-ret);
> > > >  		return ret;
> > > > @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> > > >  	return new V4L2Subdevice(mediaEntity);
> > > >  }
> > > >
> > > > -std::string V4L2Subdevice::logPrefix() const
> > > > -{
> > > > -	return "'" + entity_->name() + "'";
> > > > -}
> > > > -
> > > >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > > >  {
> > > >  	std::vector<unsigned int> codes;
> > > > @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > > >  		mbusEnum.index = index;
> > > >  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > >
> > > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > > >  		if (ret)
> > > >  			break;
> > > >
> > > >  		codes.push_back(mbusEnum.code);
> > > >  	}
> > > >
> > > > -	if (ret && errno != EINVAL) {
> > > > -		ret = errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > +	if (ret < 0 && ret != -EINVAL) {
> > > > +		LOG(V4L2, Error)
> > > >  			<< "Unable to enumerate formats on pad " << pad
> > > > -			<< ": " << strerror(ret);
> > > > +			<< ": " << strerror(-ret);
> > > >  		return {};
> > > >  	}
> > > >
> > > > @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > > >  		sizeEnum.code = code;
> > > >  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > >
> > > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > > >  		if (ret)
> > > >  			break;
> > > >
> > > > @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > > >  				   sizeEnum.max_width, sizeEnum.max_height);
> > > >  	}
> > > >
> > > > -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> > > > +		LOG(V4L2, Error)
> > > >  			<< "Unable to enumerate sizes on pad " << pad
> > > >  			<< ": " << strerror(-ret);
> > > >  		return {};
> > > > @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
> > > >  	sel.r.width = rect->w;
> > > >  	sel.r.height = rect->h;
> > > >
> > > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> > > > +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2Subdev, Error)
> > > > +		LOG(V4L2, Error)
> > > >  			<< "Unable to set rectangle " << target << " on pad "
> > > >  			<< pad << ": " << strerror(-ret);
> > > >  		return ret;
> > > > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > > > index 0e70498c8bb1..403e0b2e0653 100644
> > > > --- a/src/libcamera/v4l2_videodevice.cpp
> > > > +++ b/src/libcamera/v4l2_videodevice.cpp
> > > > @@ -30,7 +30,7 @@
> > > >   */
> > > >  namespace libcamera {
> > > >
> > > > -LOG_DEFINE_CATEGORY(V4L2)
> > > > +LOG_DECLARE_CATEGORY(V4L2)
> > > >
> > > >  /**
> > > >   * \struct V4L2Capability
> > > > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> > > >   * \param[in] deviceNode The file-system path to the video device node
> > > >   */
> > > >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > > > -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> > > > +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > > >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> > > >  {
> > > >  	/*
> > > > @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
> > > >  {
> > > >  	int ret;
> > > >
> > > > -	if (isOpen()) {
> > > > -		LOG(V4L2, Error) << "Device already open";
> > > > -		return -EBUSY;
> > > > -	}
> > > > -
> > > > -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> > > > -	if (ret < 0) {
> > > > -		ret = -errno;
> > > > -		LOG(V4L2, Error)
> > > > -			<< "Failed to open V4L2 device: " << strerror(-ret);
> > > > +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> > > > +	if (ret < 0)
> > > >  		return ret;
> > > > -	}
> > > > -	fd_ = ret;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> > > > +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to query device capabilities: "
> > > >  			<< strerror(-ret);
> > > > @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
> > > >  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
> > > >  	 */
> > > >  	if (caps_.isVideoCapture()) {
> > > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > > >  		bufferType_ = caps_.isMultiplanar()
> > > >  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> > > >  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > > >  	} else if (caps_.isVideoOutput()) {
> > > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > > >  		bufferType_ = caps_.isMultiplanar()
> > > >  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> > > >  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> > > >  	} else if (caps_.isMetaCapture()) {
> > > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > > >  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
> > > >  	} else if (caps_.isMetaOutput()) {
> > > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > > >  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
> > > >  	} else {
> > > >  		LOG(V4L2, Error) << "Device is not a supported type";
> > > > @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
> > > >  	return 0;
> > > >  }
> > > >
> > > > -/**
> > > > - * \brief Check if device is successfully opened
> > > > - * \return True if the device is open, false otherwise
> > > > - */
> > > > -bool V4L2VideoDevice::isOpen() const
> > > > -{
> > > > -	return fd_ != -1;
> > > > -}
> > > > -
> > > >  /**
> > > >   * \brief Close the device, releasing any resources acquired by open()
> > > >   */
> > > >  void V4L2VideoDevice::close()
> > > >  {
> > > > -	if (fd_ < 0)
> > > > +	if (!isOpen())
> > > >  		return;
> > > >
> > > >  	releaseBuffers();
> > > >  	delete fdEvent_;
> > > >
> > > > -	::close(fd_);
> > > > -	fd_ = -1;
> > > > +	V4L2Device::close();
> > > >  }
> > > >
> > > >  /**
> > > > @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
> > > >   * \return The string containing the device location
> > > >   */
> > > >
> > > > -/**
> > > > - * \fn V4L2VideoDevice::deviceNode()
> > > > - * \brief Retrieve the video device node path
> > > > - * \return The video device device node path
> > > > - */
> > > > -
> > > > -std::string V4L2VideoDevice::logPrefix() const
> > > > -{
> > > > -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > > > -}
> > > > -
> > > >  /**
> > > >   * \brief Retrieve the image format set on the V4L2 device
> > > >   * \param[out] format The image format applied on the device
> > > > @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
> > > >  	int ret;
> > > >
> > > >  	v4l2Format.type = bufferType_;
> > > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
> > > >  	v4l2Format.type = bufferType_;
> > > >  	pix->dataformat = format->fourcc;
> > > >  	pix->buffersize = format->planes[0].size;
> > > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
> > > >  	int ret;
> > > >
> > > >  	v4l2Format.type = bufferType_;
> > > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
> > > >  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
> > > >  	}
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
> > > >  	int ret;
> > > >
> > > >  	v4l2Format.type = bufferType_;
> > > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
> > > >  	pix->pixelformat = format->fourcc;
> > > >  	pix->bytesperline = format->planes[0].bpl;
> > > >  	pix->field = V4L2_FIELD_NONE;
> > > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > > >  	if (ret) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > > >  		return ret;
> > > >  	}
> > > > @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
> > > >  	rb.type = bufferType_;
> > > >  	rb.memory = memoryType_;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> > > > +	ret = ioctl(VIDIOC_REQBUFS, &rb);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Unable to request " << count << " buffers: "
> > > >  			<< strerror(-ret);
> > > > @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
> > > >  		buf.length = VIDEO_MAX_PLANES;
> > > >  		buf.m.planes = planes;
> > > >
> > > > -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> > > > +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
> > > >  		if (ret < 0) {
> > > > -			ret = -errno;
> > > >  			LOG(V4L2, Error)
> > > >  				<< "Unable to query buffer " << i << ": "
> > > >  				<< strerror(-ret);
> > > > @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
> > > >  	expbuf.plane = planeIndex;
> > > >  	expbuf.flags = O_RDWR;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> > > > +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to export buffer: " << strerror(-ret);
> > > >  		return ret;
> > > > @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
> > > >  		pixelformatEnum.index = index;
> > > >  		pixelformatEnum.type = bufferType_;
> > > >
> > > > -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> > > > +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
> > > >  		if (ret)
> > > >  			break;
> > > >
> > > >  		formats.push_back(pixelformatEnum.pixelformat);
> > > >  	}
> > > >
> > > > -	if (ret && errno != EINVAL) {
> > > > -		ret = -errno;
> > > > +	if (ret && ret != -EINVAL) {
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Unable to enumerate pixel formats: "
> > > >  			<< strerror(-ret);
> > > > @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > > >  		frameSize.index = index;
> > > >  		frameSize.pixel_format = pixelFormat;
> > > >
> > > > -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > > > +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > > >  		if (ret)
> > > >  			break;
> > > >
> > > > @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > > >  		}
> > > >  	}
> > > >
> > > > -	if (ret && errno != EINVAL) {
> > > > -		ret = -errno;
> > > > +	if (ret && ret != -EINVAL) {
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Unable to enumerate frame sizes: "
> > > >  			<< strerror(-ret);
> > > > @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
> > > >
> > > >  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> > > > +	ret = ioctl(VIDIOC_QBUF, &buf);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to queue buffer " << buf.index << ": "
> > > >  			<< strerror(-ret);
> > > > @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
> > > >  		buf.m.planes = planes;
> > > >  	}
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> > > > +	ret = ioctl(VIDIOC_DQBUF, &buf);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to dequeue buffer: " << strerror(-ret);
> > > >  		return nullptr;
> > > > @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
> > > >  {
> > > >  	int ret;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> > > > +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to start streaming: " << strerror(-ret);
> > > >  		return ret;
> > > > @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
> > > >  {
> > > >  	int ret;
> > > >
> > > > -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> > > > +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
> > > >  	if (ret < 0) {
> > > > -		ret = -errno;
> > > >  		LOG(V4L2, Error)
> > > >  			<< "Failed to stop streaming: " << strerror(-ret);
> > > >  		return ret;
> > >
> > > --
> > > Regards,
> > >
> > > Laurent Pinchart
>
>
>
> --
> Regards,
>
> Laurent Pinchart
Jacopo Mondi June 19, 2019, 1:07 p.m. UTC | #5
Hi
Laurent,
On Wed, Jun 19, 2019 at 03:20:29PM +0300, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> > The V4L2 devices and subdevices share a few common operations,like
> > opening and closing a device node, and perform IOCTLs on the device.
> >
> > With the forthcoming introduction of support for V4L2 controls, the
> > quantity of shared code will increase, as the control support
> > implementation is identical for the two derived classes.
> >
> > To maximize code re-use and avoid duplications, provide a V4L2Device
> > base class which groups the common operations and members.
> >
> > The newly introduced base class provides methods to open/close a device
> > node, access the file descriptor, and perform IOCTLs on the device.
> >

With your patch squashed on top and the other comment addressed, can I
have your tag (and push) or would you like another iteration?

> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > ---
> >  src/libcamera/include/v4l2_device.h      |  43 +++++++
> >  src/libcamera/include/v4l2_subdevice.h   |   9 +-
> >  src/libcamera/include/v4l2_videodevice.h |  10 +-
> >  src/libcamera/meson.build                |   2 +
> >  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
> >  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
> >  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
> >  7 files changed, 239 insertions(+), 156 deletions(-)
> >  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..2c26f3eae2b4
> > --- /dev/null
> > +++ b/src/libcamera/include/v4l2_device.h
> > @@ -0,0 +1,43 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> > + */
> > +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> > +#define __LIBCAMERA_V4L2_DEVICE_H__
> > +
> > +#include <string>
> > +
> > +#include "log.h"
> > +
> > +namespace libcamera {
> > +
> > +class V4L2Device : protected Loggable
> > +{
> > +public:
> > +	void close();
> > +	bool isOpen() const { return fd_ != -1; }
> > +	const std::string &deviceNode() const { return deviceNode_; }
> > +
> > +protected:
> > +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > +	~V4L2Device() {}
> > +
> > +	int fd() { return fd_; }
> > +
> > +	int open(unsigned int flags);
> > +
> > +	int ioctl(unsigned long request, void *argp);
> > +
> > +	std::string logPrefix() const { return "'" + logTag_ + "'"; }
>
> Let's try not to inline methods that are more complex than just
> returning a member of the class.
>
> I didn't notice in my previous review that V4L2Subdevice was using the
> entity name as a prefix. I'm afraid my comment about moving logPrefix()
> to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
> from Loggable. It will thus have a pure virtual logPrefix() inherited
> from Loggable, requiring the derived classes to implement that method,
> but allowing LOG() statements in V4L2Device to use the log prefix.
>
> Here's the diff I came up with, it can be squashed with this patch if
> you agree on the comments.
>
> diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
> index 2c26f3eae2b4..8c83adab4d43 100644
> --- a/src/libcamera/include/v4l2_device.h
> +++ b/src/libcamera/include/v4l2_device.h
> @@ -21,7 +21,7 @@ public:
>  	const std::string &deviceNode() const { return deviceNode_; }
>
>  protected:
> -	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> +	V4L2Device(const std::string &deviceNode);
>  	~V4L2Device() {}
>
>  	int fd() { return fd_; }
> @@ -30,11 +30,8 @@ protected:
>
>  	int ioctl(unsigned long request, void *argp);
>
> -	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> -
>  private:
>  	std::string deviceNode_;
> -	std::string logTag_;
>  	int fd_;
>  };
>
> diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> index b1da4a87c553..9c077674f997 100644
> --- a/src/libcamera/include/v4l2_subdevice.h
> +++ b/src/libcamera/include/v4l2_subdevice.h
> @@ -52,6 +52,9 @@ public:
>  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
>  					     const std::string &entity);
>
> +protected:
> +	std::string logPrefix() const;
> +
>  private:
>  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
>  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> index e73dec13f847..734b34f1dc53 100644
> --- a/src/libcamera/include/v4l2_videodevice.h
> +++ b/src/libcamera/include/v4l2_videodevice.h
> @@ -147,6 +147,9 @@ public:
>  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
>  					       const std::string &entity);
>
> +protected:
> +	std::string logPrefix() const;
> +
>  private:
>  	int getFormatMeta(V4L2DeviceFormat *format);
>  	int setFormatMeta(V4L2DeviceFormat *format);
> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> index eeed0a70fb0e..1f113780ba45 100644
> --- a/src/libcamera/v4l2_device.cpp
> +++ b/src/libcamera/v4l2_device.cpp
> @@ -68,17 +68,15 @@ void V4L2Device::close()
>  /**
>   * \brief Construct a V4L2Device
>   * \param[in] deviceNode The device node filesystem path
> - * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> - * the device in the log output
>   *
>   * The V4L2Device constructor is protected and can only be accessed by the
>   * V4L2VideoDevice and V4L2Subdevice derived classes.
>   *
>   * Initialize the file descriptor to -1 and store the \a deviceNode to be used
>   * at open() time, and the \a logTag to prefix log messages with.
>   */
> -V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> -	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> +V4L2Device::V4L2Device(const std::string &deviceNode)
> +	: deviceNode_(deviceNode), fd_(-1)
>  {
>  }
>
> @@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
>   */
>  int V4L2Device::open(unsigned int flags)
>  {
> +	LOG(V4L2, Info) << "Opening device";
>  	if (isOpen()) {
>  		LOG(V4L2, Error) << "Device already open";
>  		return -EBUSY;
> @@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
>  	return 0;
>  }
>
> -/**
> - * \fn V4L2Device::logPrefix()
> - * \brief Retrieve the tag to prefix log messages with
> - * \return The tag to prefix log messages with
> - */
> -
>  } /* namespace libcamera */
> diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> index d0e1d717b26c..a188298de34c 100644
> --- a/src/libcamera/v4l2_subdevice.cpp
> +++ b/src/libcamera/v4l2_subdevice.cpp
> @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
>   * path
>   */
>  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> -	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> +	: V4L2Device(entity->deviceNode()), entity_(entity)
>  {
>  }
>
> @@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
>  	return new V4L2Subdevice(mediaEntity);
>  }
>
> +std::string V4L2Subdevice::logPrefix() const
> +{
> +	return "'" + entity_->name() + "'";
> +}
> +
>  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
>  {
>  	std::vector<unsigned int> codes;
> diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> index 403e0b2e0653..2de3751f467e 100644
> --- a/src/libcamera/v4l2_videodevice.cpp
> +++ b/src/libcamera/v4l2_videodevice.cpp
> @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
>   * \param[in] deviceNode The file-system path to the video device node
>   */
>  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> -	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> +	: V4L2Device(deviceNode), bufferPool_(nullptr),
>  	  queuedBuffersCount_(0), fdEvent_(nullptr)
>  {
>  	/*
> @@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
>   * \return The string containing the device location
>   */
>
> +std::string V4L2VideoDevice::logPrefix() const
> +{
> +	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> +}
> +
>  /**
>   * \brief Retrieve the image format set on the V4L2 device
>   * \param[out] format The image format applied on the device
>
> > +
> > +private:
> > +	std::string deviceNode_;
> > +	std::string logTag_;
> > +	int fd_;
> > +};
> > +
> > +} /* namespace libcamera */
> > +
> > +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > index 9afd28b6db02..b1da4a87c553 100644
> > --- a/src/libcamera/include/v4l2_subdevice.h
> > +++ b/src/libcamera/include/v4l2_subdevice.h
> > @@ -16,6 +16,7 @@
> >  #include "formats.h"
> >  #include "log.h"
> >  #include "media_object.h"
> > +#include "v4l2_device.h"
> >
> >  namespace libcamera {
> >
> > @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
> >  	const std::string toString() const;
> >  };
> >
> > -class V4L2Subdevice : protected Loggable
> > +class V4L2Subdevice : public V4L2Device
> >  {
> >  public:
> >  	explicit V4L2Subdevice(const MediaEntity *entity);
> > @@ -37,8 +38,6 @@ public:
> >  	~V4L2Subdevice();
> >
> >  	int open();
> > -	bool isOpen() const;
> > -	void close();
> >
> >  	const MediaEntity *entity() const { return entity_; }
> >
> > @@ -53,9 +52,6 @@ public:
> >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> >  					     const std::string &entity);
> >
> > -protected:
> > -	std::string logPrefix() const;
> > -
> >  private:
> >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > @@ -65,7 +61,6 @@ private:
> >  			 Rectangle *rect);
> >
> >  	const MediaEntity *entity_;
> > -	int fd_;
> >  };
> >
> >  } /* namespace libcamera */
> > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > index 895658805b47..e73dec13f847 100644
> > --- a/src/libcamera/include/v4l2_videodevice.h
> > +++ b/src/libcamera/include/v4l2_videodevice.h
> > @@ -18,6 +18,7 @@
> >
> >  #include "formats.h"
> >  #include "log.h"
> > +#include "v4l2_device.h"
> >
> >  namespace libcamera {
> >
> > @@ -112,7 +113,7 @@ public:
> >  	const std::string toString() const;
> >  };
> >
> > -class V4L2VideoDevice : protected Loggable
> > +class V4L2VideoDevice : public V4L2Device
> >  {
> >  public:
> >  	explicit V4L2VideoDevice(const std::string &deviceNode);
> > @@ -123,13 +124,11 @@ public:
> >  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> > -	const std::string &deviceNode() const { return deviceNode_; }
> >
> >  	int getFormat(V4L2DeviceFormat *format);
> >  	int setFormat(V4L2DeviceFormat *format);
> > @@ -148,9 +147,6 @@ public:
> >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> >  					       const std::string &entity);
> >
> > -protected:
> > -	std::string logPrefix() const;
> > -
> >  private:
> >  	int getFormatMeta(V4L2DeviceFormat *format);
> >  	int setFormatMeta(V4L2DeviceFormat *format);
> > @@ -171,8 +167,6 @@ private:
> >  	Buffer *dequeueBuffer();
> >  	void bufferAvailable(EventNotifier *notifier);
> >
> > -	std::string deviceNode_;
> > -	int fd_;
> >  	V4L2Capability caps_;
> >
> >  	enum v4l2_buf_type bufferType_;
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > index 15ab53b1abbe..f26ad5b2dc57 100644
> > --- a/src/libcamera/meson.build
> > +++ b/src/libcamera/meson.build
> > @@ -23,6 +23,7 @@ libcamera_sources = files([
> >      'stream.cpp',
> >      'timer.cpp',
> >      'utils.cpp',
> > +    'v4l2_device.cpp',
> >      'v4l2_subdevice.cpp',
> >      'v4l2_videodevice.cpp',
> >  ])
> > @@ -41,6 +42,7 @@ libcamera_headers = files([
> >      'include/media_object.h',
> >      'include/pipeline_handler.h',
> >      'include/utils.h',
> > +    'include/v4l2_device.h',
> >      'include/v4l2_subdevice.h',
> >      'include/v4l2_videodevice.h',
> >  ])
> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > new file mode 100644
> > index 000000000000..eeed0a70fb0e
> > --- /dev/null
> > +++ b/src/libcamera/v4l2_device.cpp
> > @@ -0,0 +1,144 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> > + */
> > +
> > +#include "v4l2_device.h"
> > +
> > +#include <fcntl.h>
> > +#include <string.h>
> > +#include <sys/ioctl.h>
> > +#include <unistd.h>
> > +
> > +#include "log.h"
> > +
> > +/**
> > + * \file v4l2_device.h
> > + * \brief Common base for V4L2 devices and subdevices
> > + */
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(V4L2)
> > +
> > +/**
> > + * \class V4L2Device
> > + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> > + *
> > + * The V4L2Device class groups together the methods and fields common to
> > + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> > + * class whith methods to open and close the device node associated with the
> > + * device and to perform IOCTL system calls on it.
> > + *
> > + * The V4L2Device class cannot be instantiated directly, as its constructor
> > + * is protected. Users should instead create instances of one the derived
> > + * classes to model either a V4L2 video device or a V4L2 subdevice.
> > + */
> > +
> > +/**
> > + * \brief Close the device node
> > + *
> > + * Reset the file descriptor to -1
> > + */
> > +void V4L2Device::close()
> > +{
> > +	if (!isOpen())
> > +		return;
> > +
> > +	if (::close(fd_) < 0)
> > +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> > +				 << strerror(errno);
> > +	fd_ = -1;
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::isOpen()
> > + * \brief Check if the V4L2 device node is open
> > + * \return True if the V4L2 device node is open, false otherwise
> > + */
> > +
> > +/**
> > + * \fn V4L2Device::deviceNode()
> > + * \brief Retrieve the device node path
> > + * \return The device node path
> > + */
> > +
> > +/**
> > + * \brief Construct a V4L2Device
> > + * \param[in] deviceNode The device node filesystem path
> > + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > + * the device in the log output
> > + *
> > + * The V4L2Device constructor is protected and can only be accessed by the
> > + * V4L2VideoDevice and V4L2Subdevice derived classes.
>
> I would drop this sentence.
>
> > + *
> > + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > + * at open() time, and the \a logTag to prefix log messages with.
> > + */
> > +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > +{
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::fd()
> > + * \brief Retrieve the V4L2 device file descriptor
> > + * \return The V4L2 device file descriptor, -1 if the device node is not open
> > + */
> > +
> > +/**
> > + * \brief Open a V4L2 device node
> > + * \param[in] flags Access mode flags
> > + *
> > + * Open the device node path with the provided access mode \a flags and
> > + * initialize the file descriptor, which was initially set to -1.
> > + *
> > + * \return 0 on success or a negative error code otherwise
> > + */
> > +int V4L2Device::open(unsigned int flags)
> > +{
> > +	if (isOpen()) {
> > +		LOG(V4L2, Error) << "Device already open";
> > +		return -EBUSY;
> > +	}
> > +
> > +	int ret = ::open(deviceNode_.c_str(), flags);
> > +	if (ret < 0) {
> > +		ret = -errno;
> > +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> > +				 << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	fd_ = ret;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \brief Perform an IOCTL system call on the device node
> > + * \param[in] request The IOCTL request code
> > + * \param[in] argp A pointer to the IOCTL argument
> > + * \return 0 on success or a negative error code otherwise
> > + */
> > +int V4L2Device::ioctl(unsigned long request, void *argp)
> > +{
> > +	/*
> > +	 * Printing out an error message is usually better performed
> > +	 * in the caller, which can provide more context.
> > +	 */
> > +	if (::ioctl(fd_, request, argp) < 0)
> > +		return -errno;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \fn V4L2Device::logPrefix()
> > + * \brief Retrieve the tag to prefix log messages with
> > + * \return The tag to prefix log messages with
> > + */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > index 48f1fcb30ed4..d0e1d717b26c 100644
> > --- a/src/libcamera/v4l2_subdevice.cpp
> > +++ b/src/libcamera/v4l2_subdevice.cpp
> > @@ -29,7 +29,7 @@
> >
> >  namespace libcamera {
> >
> > -LOG_DEFINE_CATEGORY(V4L2Subdev)
> > +LOG_DECLARE_CATEGORY(V4L2)
> >
> >  /**
> >   * \struct V4L2SubdeviceFormat
> > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> >   * path
> >   */
> >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > -	: entity_(entity), fd_(-1)
> > +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> >  {
> >  }
> >
> > @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
> >   */
> >  int V4L2Subdevice::open()
> >  {
> > -	int ret;
> > -
> > -	if (isOpen()) {
> > -		LOG(V4L2Subdev, Error) << "Device already open";
> > -		return -EBUSY;
> > -	}
> > -
> > -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> > -	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > -			<< "Failed to open V4L2 subdevice '"
> > -			<< entity_->deviceNode() << "': " << strerror(-ret);
> > -		return ret;
> > -	}
> > -	fd_ = ret;
> > -
> > -	return 0;
> > -}
> > -
> > -/**
> > - * \brief Check if the subdevice is open
> > - * \return True if the subdevice is open, false otherwise
> > - */
> > -bool V4L2Subdevice::isOpen() const
> > -{
> > -	return fd_ != -1;
> > -}
> > -
> > -/**
> > - * \brief Close the subdevice, releasing any resources acquired by open()
> > - */
> > -void V4L2Subdevice::close()
> > -{
> > -	if (!isOpen())
> > -		return;
> > -
> > -	::close(fd_);
> > -	fd_ = -1;
> > +	return V4L2Device::open(O_RDWR);
> >  }
> >
> >  /**
> > @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> >  	ImageFormats formats;
> >
> >  	if (pad >= entity_->pads().size()) {
> > -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> > +		LOG(V4L2, Error) << "Invalid pad: " << pad;
> >  		return {};
> >  	}
> >
> > @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> >  			return {};
> >
> >  		if (formats.addFormat(code, sizes)) {
> > -			LOG(V4L2Subdev, Error)
> > +			LOG(V4L2, Error)
> >  				<< "Could not add sizes for media bus code "
> >  				<< code << " on pad " << pad;
> >  			return {};
> > @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> >  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >  	subdevFmt.pad = pad;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> >  	if (ret) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to get format on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return ret;
> > @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> >  	subdevFmt.format.height = format->size.height;
> >  	subdevFmt.format.code = format->mbus_code;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> >  	if (ret) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to set format on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return ret;
> > @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> >  	return new V4L2Subdevice(mediaEntity);
> >  }
> >
> > -std::string V4L2Subdevice::logPrefix() const
> > -{
> > -	return "'" + entity_->name() + "'";
> > -}
> > -
> >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  {
> >  	std::vector<unsigned int> codes;
> > @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  		mbusEnum.index = index;
> >  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >
> > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> >  		if (ret)
> >  			break;
> >
> >  		codes.push_back(mbusEnum.code);
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = errno;
> > -		LOG(V4L2Subdev, Error)
> > +	if (ret < 0 && ret != -EINVAL) {
> > +		LOG(V4L2, Error)
> >  			<< "Unable to enumerate formats on pad " << pad
> > -			<< ": " << strerror(ret);
> > +			<< ": " << strerror(-ret);
> >  		return {};
> >  	}
> >
> > @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> >  		sizeEnum.code = code;
> >  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> >
> > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> >  		if (ret)
> >  			break;
> >
> > @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> >  				   sizeEnum.max_width, sizeEnum.max_height);
> >  	}
> >
> > -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> > +		LOG(V4L2, Error)
> >  			<< "Unable to enumerate sizes on pad " << pad
> >  			<< ": " << strerror(-ret);
> >  		return {};
> > @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
> >  	sel.r.width = rect->w;
> >  	sel.r.height = rect->h;
> >
> > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> > +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
> >  	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2Subdev, Error)
> > +		LOG(V4L2, Error)
> >  			<< "Unable to set rectangle " << target << " on pad "
> >  			<< pad << ": " << strerror(-ret);
> >  		return ret;
> > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > index 0e70498c8bb1..403e0b2e0653 100644
> > --- a/src/libcamera/v4l2_videodevice.cpp
> > +++ b/src/libcamera/v4l2_videodevice.cpp
> > @@ -30,7 +30,7 @@
> >   */
> >  namespace libcamera {
> >
> > -LOG_DEFINE_CATEGORY(V4L2)
> > +LOG_DECLARE_CATEGORY(V4L2)
> >
> >  /**
> >   * \struct V4L2Capability
> > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> >   * \param[in] deviceNode The file-system path to the video device node
> >   */
> >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> > +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> >  {
> >  	/*
> > @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
> >  {
> >  	int ret;
> >
> > -	if (isOpen()) {
> > -		LOG(V4L2, Error) << "Device already open";
> > -		return -EBUSY;
> > -	}
> > -
> > -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> > -	if (ret < 0) {
> > -		ret = -errno;
> > -		LOG(V4L2, Error)
> > -			<< "Failed to open V4L2 device: " << strerror(-ret);
> > +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> > +	if (ret < 0)
> >  		return ret;
> > -	}
> > -	fd_ = ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> > +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to query device capabilities: "
> >  			<< strerror(-ret);
> > @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
> >  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
> >  	 */
> >  	if (caps_.isVideoCapture()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> >  		bufferType_ = caps_.isMultiplanar()
> >  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> >  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> >  	} else if (caps_.isVideoOutput()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> >  		bufferType_ = caps_.isMultiplanar()
> >  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> >  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> >  	} else if (caps_.isMetaCapture()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> >  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
> >  	} else if (caps_.isMetaOutput()) {
> > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> >  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
> >  	} else {
> >  		LOG(V4L2, Error) << "Device is not a supported type";
> > @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
> >  	return 0;
> >  }
> >
> > -/**
> > - * \brief Check if device is successfully opened
> > - * \return True if the device is open, false otherwise
> > - */
> > -bool V4L2VideoDevice::isOpen() const
> > -{
> > -	return fd_ != -1;
> > -}
> > -
> >  /**
> >   * \brief Close the device, releasing any resources acquired by open()
> >   */
> >  void V4L2VideoDevice::close()
> >  {
> > -	if (fd_ < 0)
> > +	if (!isOpen())
> >  		return;
> >
> >  	releaseBuffers();
> >  	delete fdEvent_;
> >
> > -	::close(fd_);
> > -	fd_ = -1;
> > +	V4L2Device::close();
> >  }
> >
> >  /**
> > @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
> >   * \return The string containing the device location
> >   */
> >
> > -/**
> > - * \fn V4L2VideoDevice::deviceNode()
> > - * \brief Retrieve the video device node path
> > - * \return The video device device node path
> > - */
> > -
> > -std::string V4L2VideoDevice::logPrefix() const
> > -{
> > -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > -}
> > -
> >  /**
> >   * \brief Retrieve the image format set on the V4L2 device
> >   * \param[out] format The image format applied on the device
> > @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
> >  	v4l2Format.type = bufferType_;
> >  	pix->dataformat = format->fourcc;
> >  	pix->buffersize = format->planes[0].size;
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
> >  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
> >  	}
> >
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
> >  	int ret;
> >
> >  	v4l2Format.type = bufferType_;
> > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
> >  	pix->pixelformat = format->fourcc;
> >  	pix->bytesperline = format->planes[0].bpl;
> >  	pix->field = V4L2_FIELD_NONE;
> > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> >  	if (ret) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> >  		return ret;
> >  	}
> > @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
> >  	rb.type = bufferType_;
> >  	rb.memory = memoryType_;
> >
> > -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> > +	ret = ioctl(VIDIOC_REQBUFS, &rb);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Unable to request " << count << " buffers: "
> >  			<< strerror(-ret);
> > @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
> >  		buf.length = VIDEO_MAX_PLANES;
> >  		buf.m.planes = planes;
> >
> > -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> > +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
> >  		if (ret < 0) {
> > -			ret = -errno;
> >  			LOG(V4L2, Error)
> >  				<< "Unable to query buffer " << i << ": "
> >  				<< strerror(-ret);
> > @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
> >  	expbuf.plane = planeIndex;
> >  	expbuf.flags = O_RDWR;
> >
> > -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> > +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to export buffer: " << strerror(-ret);
> >  		return ret;
> > @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
> >  		pixelformatEnum.index = index;
> >  		pixelformatEnum.type = bufferType_;
> >
> > -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> > +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
> >  		if (ret)
> >  			break;
> >
> >  		formats.push_back(pixelformatEnum.pixelformat);
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = -errno;
> > +	if (ret && ret != -EINVAL) {
> >  		LOG(V4L2, Error)
> >  			<< "Unable to enumerate pixel formats: "
> >  			<< strerror(-ret);
> > @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> >  		frameSize.index = index;
> >  		frameSize.pixel_format = pixelFormat;
> >
> > -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
> >  		if (ret)
> >  			break;
> >
> > @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> >  		}
> >  	}
> >
> > -	if (ret && errno != EINVAL) {
> > -		ret = -errno;
> > +	if (ret && ret != -EINVAL) {
> >  		LOG(V4L2, Error)
> >  			<< "Unable to enumerate frame sizes: "
> >  			<< strerror(-ret);
> > @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
> >
> >  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
> >
> > -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> > +	ret = ioctl(VIDIOC_QBUF, &buf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to queue buffer " << buf.index << ": "
> >  			<< strerror(-ret);
> > @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
> >  		buf.m.planes = planes;
> >  	}
> >
> > -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> > +	ret = ioctl(VIDIOC_DQBUF, &buf);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to dequeue buffer: " << strerror(-ret);
> >  		return nullptr;
> > @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
> >  {
> >  	int ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> > +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to start streaming: " << strerror(-ret);
> >  		return ret;
> > @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
> >  {
> >  	int ret;
> >
> > -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> > +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
> >  	if (ret < 0) {
> > -		ret = -errno;
> >  		LOG(V4L2, Error)
> >  			<< "Failed to stop streaming: " << strerror(-ret);
> >  		return ret;
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart June 19, 2019, 1:07 p.m. UTC | #6
Hi Jacopo,

On Wed, Jun 19, 2019 at 03:07:27PM +0200, Jacopo Mondi wrote:
> On Wed, Jun 19, 2019 at 03:20:29PM +0300, Laurent Pinchart wrote:
> > On Wed, Jun 19, 2019 at 01:05:46PM +0200, Jacopo Mondi wrote:
> > > The V4L2 devices and subdevices share a few common operations,like
> > > opening and closing a device node, and perform IOCTLs on the device.
> > >
> > > With the forthcoming introduction of support for V4L2 controls, the
> > > quantity of shared code will increase, as the control support
> > > implementation is identical for the two derived classes.
> > >
> > > To maximize code re-use and avoid duplications, provide a V4L2Device
> > > base class which groups the common operations and members.
> > >
> > > The newly introduced base class provides methods to open/close a device
> > > node, access the file descriptor, and perform IOCTLs on the device.
> > >
> 
> With your patch squashed on top and the other comment addressed, can I
> have your tag (and push) or would you like another iteration?

I was thinking on commenting about the order of methods in the
v4l2_device.cpp file, but that can be addressed later, so

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > ---
> > >  src/libcamera/include/v4l2_device.h      |  43 +++++++
> > >  src/libcamera/include/v4l2_subdevice.h   |   9 +-
> > >  src/libcamera/include/v4l2_videodevice.h |  10 +-
> > >  src/libcamera/meson.build                |   2 +
> > >  src/libcamera/v4l2_device.cpp            | 144 +++++++++++++++++++++++
> > >  src/libcamera/v4l2_subdevice.cpp         |  84 +++----------
> > >  src/libcamera/v4l2_videodevice.cpp       | 103 +++++-----------
> > >  7 files changed, 239 insertions(+), 156 deletions(-)
> > >  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..2c26f3eae2b4
> > > --- /dev/null
> > > +++ b/src/libcamera/include/v4l2_device.h
> > > @@ -0,0 +1,43 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2019, Google Inc.
> > > + *
> > > + * v4l2_device.h - Common base for V4L2 video devices and subdevices
> > > + */
> > > +#ifndef __LIBCAMERA_V4L2_DEVICE_H__
> > > +#define __LIBCAMERA_V4L2_DEVICE_H__
> > > +
> > > +#include <string>
> > > +
> > > +#include "log.h"
> > > +
> > > +namespace libcamera {
> > > +
> > > +class V4L2Device : protected Loggable
> > > +{
> > > +public:
> > > +	void close();
> > > +	bool isOpen() const { return fd_ != -1; }
> > > +	const std::string &deviceNode() const { return deviceNode_; }
> > > +
> > > +protected:
> > > +	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > > +	~V4L2Device() {}
> > > +
> > > +	int fd() { return fd_; }
> > > +
> > > +	int open(unsigned int flags);
> > > +
> > > +	int ioctl(unsigned long request, void *argp);
> > > +
> > > +	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> >
> > Let's try not to inline methods that are more complex than just
> > returning a member of the class.
> >
> > I didn't notice in my previous review that V4L2Subdevice was using the
> > entity name as a prefix. I'm afraid my comment about moving logPrefix()
> > to V4L2Device was a bad one :-/ However, V4L2Device should still inherit
> > from Loggable. It will thus have a pure virtual logPrefix() inherited
> > from Loggable, requiring the derived classes to implement that method,
> > but allowing LOG() statements in V4L2Device to use the log prefix.
> >
> > Here's the diff I came up with, it can be squashed with this patch if
> > you agree on the comments.
> >
> > diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
> > index 2c26f3eae2b4..8c83adab4d43 100644
> > --- a/src/libcamera/include/v4l2_device.h
> > +++ b/src/libcamera/include/v4l2_device.h
> > @@ -21,7 +21,7 @@ public:
> >  	const std::string &deviceNode() const { return deviceNode_; }
> >
> >  protected:
> > -	V4L2Device(const std::string &deviceNode, const std::string &logTag);
> > +	V4L2Device(const std::string &deviceNode);
> >  	~V4L2Device() {}
> >
> >  	int fd() { return fd_; }
> > @@ -30,11 +30,8 @@ protected:
> >
> >  	int ioctl(unsigned long request, void *argp);
> >
> > -	std::string logPrefix() const { return "'" + logTag_ + "'"; }
> > -
> >  private:
> >  	std::string deviceNode_;
> > -	std::string logTag_;
> >  	int fd_;
> >  };
> >
> > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > index b1da4a87c553..9c077674f997 100644
> > --- a/src/libcamera/include/v4l2_subdevice.h
> > +++ b/src/libcamera/include/v4l2_subdevice.h
> > @@ -52,6 +52,9 @@ public:
> >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> >  					     const std::string &entity);
> >
> > +protected:
> > +	std::string logPrefix() const;
> > +
> >  private:
> >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > index e73dec13f847..734b34f1dc53 100644
> > --- a/src/libcamera/include/v4l2_videodevice.h
> > +++ b/src/libcamera/include/v4l2_videodevice.h
> > @@ -147,6 +147,9 @@ public:
> >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> >  					       const std::string &entity);
> >
> > +protected:
> > +	std::string logPrefix() const;
> > +
> >  private:
> >  	int getFormatMeta(V4L2DeviceFormat *format);
> >  	int setFormatMeta(V4L2DeviceFormat *format);
> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > index eeed0a70fb0e..1f113780ba45 100644
> > --- a/src/libcamera/v4l2_device.cpp
> > +++ b/src/libcamera/v4l2_device.cpp
> > @@ -68,17 +68,15 @@ void V4L2Device::close()
> >  /**
> >   * \brief Construct a V4L2Device
> >   * \param[in] deviceNode The device node filesystem path
> > - * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > - * the device in the log output
> >   *
> >   * The V4L2Device constructor is protected and can only be accessed by the
> >   * V4L2VideoDevice and V4L2Subdevice derived classes.
> >   *
> >   * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> >   * at open() time, and the \a logTag to prefix log messages with.
> >   */
> > -V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > -	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > +V4L2Device::V4L2Device(const std::string &deviceNode)
> > +	: deviceNode_(deviceNode), fd_(-1)
> >  {
> >  }
> >
> > @@ -99,6 +94,7 @@ V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> >   */
> >  int V4L2Device::open(unsigned int flags)
> >  {
> > +	LOG(V4L2, Info) << "Opening device";
> >  	if (isOpen()) {
> >  		LOG(V4L2, Error) << "Device already open";
> >  		return -EBUSY;
> > @@ -135,10 +131,4 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
> >  	return 0;
> >  }
> >
> > -/**
> > - * \fn V4L2Device::logPrefix()
> > - * \brief Retrieve the tag to prefix log messages with
> > - * \return The tag to prefix log messages with
> > - */
> > -
> >  } /* namespace libcamera */
> > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > index d0e1d717b26c..a188298de34c 100644
> > --- a/src/libcamera/v4l2_subdevice.cpp
> > +++ b/src/libcamera/v4l2_subdevice.cpp
> > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> >   * path
> >   */
> >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > -	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > +	: V4L2Device(entity->deviceNode()), entity_(entity)
> >  {
> >  }
> >
> > @@ -265,6 +265,11 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> >  	return new V4L2Subdevice(mediaEntity);
> >  }
> >
> > +std::string V4L2Subdevice::logPrefix() const
> > +{
> > +	return "'" + entity_->name() + "'";
> > +}
> > +
> >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> >  {
> >  	std::vector<unsigned int> codes;
> > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > index 403e0b2e0653..2de3751f467e 100644
> > --- a/src/libcamera/v4l2_videodevice.cpp
> > +++ b/src/libcamera/v4l2_videodevice.cpp
> > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> >   * \param[in] deviceNode The file-system path to the video device node
> >   */
> >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > -	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > +	: V4L2Device(deviceNode), bufferPool_(nullptr),
> >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> >  {
> >  	/*
> > @@ -389,6 +389,11 @@ void V4L2VideoDevice::close()
> >   * \return The string containing the device location
> >   */
> >
> > +std::string V4L2VideoDevice::logPrefix() const
> > +{
> > +	return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > +}
> > +
> >  /**
> >   * \brief Retrieve the image format set on the V4L2 device
> >   * \param[out] format The image format applied on the device
> >
> > > +
> > > +private:
> > > +	std::string deviceNode_;
> > > +	std::string logTag_;
> > > +	int fd_;
> > > +};
> > > +
> > > +} /* namespace libcamera */
> > > +
> > > +#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
> > > diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
> > > index 9afd28b6db02..b1da4a87c553 100644
> > > --- a/src/libcamera/include/v4l2_subdevice.h
> > > +++ b/src/libcamera/include/v4l2_subdevice.h
> > > @@ -16,6 +16,7 @@
> > >  #include "formats.h"
> > >  #include "log.h"
> > >  #include "media_object.h"
> > > +#include "v4l2_device.h"
> > >
> > >  namespace libcamera {
> > >
> > > @@ -28,7 +29,7 @@ struct V4L2SubdeviceFormat {
> > >  	const std::string toString() const;
> > >  };
> > >
> > > -class V4L2Subdevice : protected Loggable
> > > +class V4L2Subdevice : public V4L2Device
> > >  {
> > >  public:
> > >  	explicit V4L2Subdevice(const MediaEntity *entity);
> > > @@ -37,8 +38,6 @@ public:
> > >  	~V4L2Subdevice();
> > >
> > >  	int open();
> > > -	bool isOpen() const;
> > > -	void close();
> > >
> > >  	const MediaEntity *entity() const { return entity_; }
> > >
> > > @@ -53,9 +52,6 @@ public:
> > >  	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
> > >  					     const std::string &entity);
> > >
> > > -protected:
> > > -	std::string logPrefix() const;
> > > -
> > >  private:
> > >  	std::vector<unsigned int> enumPadCodes(unsigned int pad);
> > >  	std::vector<SizeRange> enumPadSizes(unsigned int pad,
> > > @@ -65,7 +61,6 @@ private:
> > >  			 Rectangle *rect);
> > >
> > >  	const MediaEntity *entity_;
> > > -	int fd_;
> > >  };
> > >
> > >  } /* namespace libcamera */
> > > diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
> > > index 895658805b47..e73dec13f847 100644
> > > --- a/src/libcamera/include/v4l2_videodevice.h
> > > +++ b/src/libcamera/include/v4l2_videodevice.h
> > > @@ -18,6 +18,7 @@
> > >
> > >  #include "formats.h"
> > >  #include "log.h"
> > > +#include "v4l2_device.h"
> > >
> > >  namespace libcamera {
> > >
> > > @@ -112,7 +113,7 @@ public:
> > >  	const std::string toString() const;
> > >  };
> > >
> > > -class V4L2VideoDevice : protected Loggable
> > > +class V4L2VideoDevice : public V4L2Device
> > >  {
> > >  public:
> > >  	explicit V4L2VideoDevice(const std::string &deviceNode);
> > > @@ -123,13 +124,11 @@ public:
> > >  	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
> > > -	const std::string &deviceNode() const { return deviceNode_; }
> > >
> > >  	int getFormat(V4L2DeviceFormat *format);
> > >  	int setFormat(V4L2DeviceFormat *format);
> > > @@ -148,9 +147,6 @@ public:
> > >  	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
> > >  					       const std::string &entity);
> > >
> > > -protected:
> > > -	std::string logPrefix() const;
> > > -
> > >  private:
> > >  	int getFormatMeta(V4L2DeviceFormat *format);
> > >  	int setFormatMeta(V4L2DeviceFormat *format);
> > > @@ -171,8 +167,6 @@ private:
> > >  	Buffer *dequeueBuffer();
> > >  	void bufferAvailable(EventNotifier *notifier);
> > >
> > > -	std::string deviceNode_;
> > > -	int fd_;
> > >  	V4L2Capability caps_;
> > >
> > >  	enum v4l2_buf_type bufferType_;
> > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > > index 15ab53b1abbe..f26ad5b2dc57 100644
> > > --- a/src/libcamera/meson.build
> > > +++ b/src/libcamera/meson.build
> > > @@ -23,6 +23,7 @@ libcamera_sources = files([
> > >      'stream.cpp',
> > >      'timer.cpp',
> > >      'utils.cpp',
> > > +    'v4l2_device.cpp',
> > >      'v4l2_subdevice.cpp',
> > >      'v4l2_videodevice.cpp',
> > >  ])
> > > @@ -41,6 +42,7 @@ libcamera_headers = files([
> > >      'include/media_object.h',
> > >      'include/pipeline_handler.h',
> > >      'include/utils.h',
> > > +    'include/v4l2_device.h',
> > >      'include/v4l2_subdevice.h',
> > >      'include/v4l2_videodevice.h',
> > >  ])
> > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > new file mode 100644
> > > index 000000000000..eeed0a70fb0e
> > > --- /dev/null
> > > +++ b/src/libcamera/v4l2_device.cpp
> > > @@ -0,0 +1,144 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2019, Google Inc.
> > > + *
> > > + * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
> > > + */
> > > +
> > > +#include "v4l2_device.h"
> > > +
> > > +#include <fcntl.h>
> > > +#include <string.h>
> > > +#include <sys/ioctl.h>
> > > +#include <unistd.h>
> > > +
> > > +#include "log.h"
> > > +
> > > +/**
> > > + * \file v4l2_device.h
> > > + * \brief Common base for V4L2 devices and subdevices
> > > + */
> > > +
> > > +namespace libcamera {
> > > +
> > > +LOG_DEFINE_CATEGORY(V4L2)
> > > +
> > > +/**
> > > + * \class V4L2Device
> > > + * \brief Base class for V4L2VideoDevice and V4L2Subdevice
> > > + *
> > > + * The V4L2Device class groups together the methods and fields common to
> > > + * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
> > > + * class whith methods to open and close the device node associated with the
> > > + * device and to perform IOCTL system calls on it.
> > > + *
> > > + * The V4L2Device class cannot be instantiated directly, as its constructor
> > > + * is protected. Users should instead create instances of one the derived
> > > + * classes to model either a V4L2 video device or a V4L2 subdevice.
> > > + */
> > > +
> > > +/**
> > > + * \brief Close the device node
> > > + *
> > > + * Reset the file descriptor to -1
> > > + */
> > > +void V4L2Device::close()
> > > +{
> > > +	if (!isOpen())
> > > +		return;
> > > +
> > > +	if (::close(fd_) < 0)
> > > +		LOG(V4L2, Error) << "Failed to close V4L2 device: "
> > > +				 << strerror(errno);
> > > +	fd_ = -1;
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::isOpen()
> > > + * \brief Check if the V4L2 device node is open
> > > + * \return True if the V4L2 device node is open, false otherwise
> > > + */
> > > +
> > > +/**
> > > + * \fn V4L2Device::deviceNode()
> > > + * \brief Retrieve the device node path
> > > + * \return The device node path
> > > + */
> > > +
> > > +/**
> > > + * \brief Construct a V4L2Device
> > > + * \param[in] deviceNode The device node filesystem path
> > > + * \param[in] logTag The tag to prefix log messages with. Helpful to identify
> > > + * the device in the log output
> > > + *
> > > + * The V4L2Device constructor is protected and can only be accessed by the
> > > + * V4L2VideoDevice and V4L2Subdevice derived classes.
> >
> > I would drop this sentence.
> >
> > > + *
> > > + * Initialize the file descriptor to -1 and store the \a deviceNode to be used
> > > + * at open() time, and the \a logTag to prefix log messages with.
> > > + */
> > > +V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
> > > +	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
> > > +{
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::fd()
> > > + * \brief Retrieve the V4L2 device file descriptor
> > > + * \return The V4L2 device file descriptor, -1 if the device node is not open
> > > + */
> > > +
> > > +/**
> > > + * \brief Open a V4L2 device node
> > > + * \param[in] flags Access mode flags
> > > + *
> > > + * Open the device node path with the provided access mode \a flags and
> > > + * initialize the file descriptor, which was initially set to -1.
> > > + *
> > > + * \return 0 on success or a negative error code otherwise
> > > + */
> > > +int V4L2Device::open(unsigned int flags)
> > > +{
> > > +	if (isOpen()) {
> > > +		LOG(V4L2, Error) << "Device already open";
> > > +		return -EBUSY;
> > > +	}
> > > +
> > > +	int ret = ::open(deviceNode_.c_str(), flags);
> > > +	if (ret < 0) {
> > > +		ret = -errno;
> > > +		LOG(V4L2, Error) << "Failed to open V4L2 device: "
> > > +				 << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	fd_ = ret;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \brief Perform an IOCTL system call on the device node
> > > + * \param[in] request The IOCTL request code
> > > + * \param[in] argp A pointer to the IOCTL argument
> > > + * \return 0 on success or a negative error code otherwise
> > > + */
> > > +int V4L2Device::ioctl(unsigned long request, void *argp)
> > > +{
> > > +	/*
> > > +	 * Printing out an error message is usually better performed
> > > +	 * in the caller, which can provide more context.
> > > +	 */
> > > +	if (::ioctl(fd_, request, argp) < 0)
> > > +		return -errno;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \fn V4L2Device::logPrefix()
> > > + * \brief Retrieve the tag to prefix log messages with
> > > + * \return The tag to prefix log messages with
> > > + */
> > > +
> > > +} /* namespace libcamera */
> > > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > > index 48f1fcb30ed4..d0e1d717b26c 100644
> > > --- a/src/libcamera/v4l2_subdevice.cpp
> > > +++ b/src/libcamera/v4l2_subdevice.cpp
> > > @@ -29,7 +29,7 @@
> > >
> > >  namespace libcamera {
> > >
> > > -LOG_DEFINE_CATEGORY(V4L2Subdev)
> > > +LOG_DECLARE_CATEGORY(V4L2)
> > >
> > >  /**
> > >   * \struct V4L2SubdeviceFormat
> > > @@ -102,7 +102,7 @@ const std::string V4L2SubdeviceFormat::toString() const
> > >   * path
> > >   */
> > >  V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
> > > -	: entity_(entity), fd_(-1)
> > > +	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
> > >  {
> > >  }
> > >
> > > @@ -117,45 +117,7 @@ V4L2Subdevice::~V4L2Subdevice()
> > >   */
> > >  int V4L2Subdevice::open()
> > >  {
> > > -	int ret;
> > > -
> > > -	if (isOpen()) {
> > > -		LOG(V4L2Subdev, Error) << "Device already open";
> > > -		return -EBUSY;
> > > -	}
> > > -
> > > -	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
> > > -	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > -			<< "Failed to open V4L2 subdevice '"
> > > -			<< entity_->deviceNode() << "': " << strerror(-ret);
> > > -		return ret;
> > > -	}
> > > -	fd_ = ret;
> > > -
> > > -	return 0;
> > > -}
> > > -
> > > -/**
> > > - * \brief Check if the subdevice is open
> > > - * \return True if the subdevice is open, false otherwise
> > > - */
> > > -bool V4L2Subdevice::isOpen() const
> > > -{
> > > -	return fd_ != -1;
> > > -}
> > > -
> > > -/**
> > > - * \brief Close the subdevice, releasing any resources acquired by open()
> > > - */
> > > -void V4L2Subdevice::close()
> > > -{
> > > -	if (!isOpen())
> > > -		return;
> > > -
> > > -	::close(fd_);
> > > -	fd_ = -1;
> > > +	return V4L2Device::open(O_RDWR);
> > >  }
> > >
> > >  /**
> > > @@ -200,7 +162,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > >  	ImageFormats formats;
> > >
> > >  	if (pad >= entity_->pads().size()) {
> > > -		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
> > > +		LOG(V4L2, Error) << "Invalid pad: " << pad;
> > >  		return {};
> > >  	}
> > >
> > > @@ -210,7 +172,7 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad)
> > >  			return {};
> > >
> > >  		if (formats.addFormat(code, sizes)) {
> > > -			LOG(V4L2Subdev, Error)
> > > +			LOG(V4L2, Error)
> > >  				<< "Could not add sizes for media bus code "
> > >  				<< code << " on pad " << pad;
> > >  			return {};
> > > @@ -232,10 +194,9 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > >  	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >  	subdevFmt.pad = pad;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
> > >  	if (ret) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to get format on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return ret;
> > > @@ -268,10 +229,9 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
> > >  	subdevFmt.format.height = format->size.height;
> > >  	subdevFmt.format.code = format->mbus_code;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
> > >  	if (ret) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to set format on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return ret;
> > > @@ -305,11 +265,6 @@ V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
> > >  	return new V4L2Subdevice(mediaEntity);
> > >  }
> > >
> > > -std::string V4L2Subdevice::logPrefix() const
> > > -{
> > > -	return "'" + entity_->name() + "'";
> > > -}
> > > -
> > >  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > >  {
> > >  	std::vector<unsigned int> codes;
> > > @@ -321,18 +276,17 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
> > >  		mbusEnum.index = index;
> > >  		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > >  		codes.push_back(mbusEnum.code);
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +	if (ret < 0 && ret != -EINVAL) {
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate formats on pad " << pad
> > > -			<< ": " << strerror(ret);
> > > +			<< ": " << strerror(-ret);
> > >  		return {};
> > >  	}
> > >
> > > @@ -352,7 +306,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > >  		sizeEnum.code = code;
> > >  		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > > +		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > > @@ -360,9 +314,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
> > >  				   sizeEnum.max_width, sizeEnum.max_height);
> > >  	}
> > >
> > > -	if (ret && (errno != EINVAL && errno != ENOTTY)) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate sizes on pad " << pad
> > >  			<< ": " << strerror(-ret);
> > >  		return {};
> > > @@ -386,10 +339,9 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
> > >  	sel.r.width = rect->w;
> > >  	sel.r.height = rect->h;
> > >
> > > -	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
> > > +	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2Subdev, Error)
> > > +		LOG(V4L2, Error)
> > >  			<< "Unable to set rectangle " << target << " on pad "
> > >  			<< pad << ": " << strerror(-ret);
> > >  		return ret;
> > > diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
> > > index 0e70498c8bb1..403e0b2e0653 100644
> > > --- a/src/libcamera/v4l2_videodevice.cpp
> > > +++ b/src/libcamera/v4l2_videodevice.cpp
> > > @@ -30,7 +30,7 @@
> > >   */
> > >  namespace libcamera {
> > >
> > > -LOG_DEFINE_CATEGORY(V4L2)
> > > +LOG_DECLARE_CATEGORY(V4L2)
> > >
> > >  /**
> > >   * \struct V4L2Capability
> > > @@ -270,7 +270,7 @@ const std::string V4L2DeviceFormat::toString() const
> > >   * \param[in] deviceNode The file-system path to the video device node
> > >   */
> > >  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
> > > -	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
> > > +	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
> > >  	  queuedBuffersCount_(0), fdEvent_(nullptr)
> > >  {
> > >  	/*
> > > @@ -305,23 +305,12 @@ int V4L2VideoDevice::open()
> > >  {
> > >  	int ret;
> > >
> > > -	if (isOpen()) {
> > > -		LOG(V4L2, Error) << "Device already open";
> > > -		return -EBUSY;
> > > -	}
> > > -
> > > -	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
> > > -	if (ret < 0) {
> > > -		ret = -errno;
> > > -		LOG(V4L2, Error)
> > > -			<< "Failed to open V4L2 device: " << strerror(-ret);
> > > +	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
> > > +	if (ret < 0)
> > >  		return ret;
> > > -	}
> > > -	fd_ = ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
> > > +	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to query device capabilities: "
> > >  			<< strerror(-ret);
> > > @@ -342,20 +331,20 @@ int V4L2VideoDevice::open()
> > >  	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
> > >  	 */
> > >  	if (caps_.isVideoCapture()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > >  		bufferType_ = caps_.isMultiplanar()
> > >  			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
> > >  			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
> > >  	} else if (caps_.isVideoOutput()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > >  		bufferType_ = caps_.isMultiplanar()
> > >  			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
> > >  			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
> > >  	} else if (caps_.isMetaCapture()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
> > >  		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
> > >  	} else if (caps_.isMetaOutput()) {
> > > -		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
> > > +		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
> > >  		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
> > >  	} else {
> > >  		LOG(V4L2, Error) << "Device is not a supported type";
> > > @@ -368,28 +357,18 @@ int V4L2VideoDevice::open()
> > >  	return 0;
> > >  }
> > >
> > > -/**
> > > - * \brief Check if device is successfully opened
> > > - * \return True if the device is open, false otherwise
> > > - */
> > > -bool V4L2VideoDevice::isOpen() const
> > > -{
> > > -	return fd_ != -1;
> > > -}
> > > -
> > >  /**
> > >   * \brief Close the device, releasing any resources acquired by open()
> > >   */
> > >  void V4L2VideoDevice::close()
> > >  {
> > > -	if (fd_ < 0)
> > > +	if (!isOpen())
> > >  		return;
> > >
> > >  	releaseBuffers();
> > >  	delete fdEvent_;
> > >
> > > -	::close(fd_);
> > > -	fd_ = -1;
> > > +	V4L2Device::close();
> > >  }
> > >
> > >  /**
> > > @@ -410,17 +389,6 @@ void V4L2VideoDevice::close()
> > >   * \return The string containing the device location
> > >   */
> > >
> > > -/**
> > > - * \fn V4L2VideoDevice::deviceNode()
> > > - * \brief Retrieve the video device node path
> > > - * \return The video device device node path
> > > - */
> > > -
> > > -std::string V4L2VideoDevice::logPrefix() const
> > > -{
> > > -	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
> > > -}
> > > -
> > >  /**
> > >   * \brief Retrieve the image format set on the V4L2 device
> > >   * \param[out] format The image format applied on the device
> > > @@ -462,9 +430,8 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -488,9 +455,8 @@ int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
> > >  	v4l2Format.type = bufferType_;
> > >  	pix->dataformat = format->fourcc;
> > >  	pix->buffersize = format->planes[0].size;
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -516,9 +482,8 @@ int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -554,9 +519,8 @@ int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
> > >  		pix->plane_fmt[i].sizeimage = format->planes[i].size;
> > >  	}
> > >
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -584,9 +548,8 @@ int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
> > >  	int ret;
> > >
> > >  	v4l2Format.type = bufferType_;
> > > -	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -613,9 +576,8 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
> > >  	pix->pixelformat = format->fourcc;
> > >  	pix->bytesperline = format->planes[0].bpl;
> > >  	pix->field = V4L2_FIELD_NONE;
> > > -	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
> > > +	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
> > >  	if (ret) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
> > >  		return ret;
> > >  	}
> > > @@ -670,9 +632,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
> > >  	rb.type = bufferType_;
> > >  	rb.memory = memoryType_;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
> > > +	ret = ioctl(VIDIOC_REQBUFS, &rb);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to request " << count << " buffers: "
> > >  			<< strerror(-ret);
> > > @@ -722,9 +683,8 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
> > >  		buf.length = VIDEO_MAX_PLANES;
> > >  		buf.m.planes = planes;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
> > > +		ret = ioctl(VIDIOC_QUERYBUF, &buf);
> > >  		if (ret < 0) {
> > > -			ret = -errno;
> > >  			LOG(V4L2, Error)
> > >  				<< "Unable to query buffer " << i << ": "
> > >  				<< strerror(-ret);
> > > @@ -775,9 +735,8 @@ int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
> > >  	expbuf.plane = planeIndex;
> > >  	expbuf.flags = O_RDWR;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
> > > +	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to export buffer: " << strerror(-ret);
> > >  		return ret;
> > > @@ -801,15 +760,14 @@ std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
> > >  		pixelformatEnum.index = index;
> > >  		pixelformatEnum.type = bufferType_;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
> > > +		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
> > >  		if (ret)
> > >  			break;
> > >
> > >  		formats.push_back(pixelformatEnum.pixelformat);
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = -errno;
> > > +	if (ret && ret != -EINVAL) {
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate pixel formats: "
> > >  			<< strerror(-ret);
> > > @@ -829,7 +787,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > >  		frameSize.index = index;
> > >  		frameSize.pixel_format = pixelFormat;
> > >
> > > -		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > > +		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
> > >  		if (ret)
> > >  			break;
> > >
> > > @@ -867,8 +825,7 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
> > >  		}
> > >  	}
> > >
> > > -	if (ret && errno != EINVAL) {
> > > -		ret = -errno;
> > > +	if (ret && ret != -EINVAL) {
> > >  		LOG(V4L2, Error)
> > >  			<< "Unable to enumerate frame sizes: "
> > >  			<< strerror(-ret);
> > > @@ -969,9 +926,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
> > >
> > >  	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
> > > +	ret = ioctl(VIDIOC_QBUF, &buf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to queue buffer " << buf.index << ": "
> > >  			<< strerror(-ret);
> > > @@ -1006,9 +962,8 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
> > >  		buf.m.planes = planes;
> > >  	}
> > >
> > > -	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
> > > +	ret = ioctl(VIDIOC_DQBUF, &buf);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to dequeue buffer: " << strerror(-ret);
> > >  		return nullptr;
> > > @@ -1066,9 +1021,8 @@ int V4L2VideoDevice::streamOn()
> > >  {
> > >  	int ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
> > > +	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to start streaming: " << strerror(-ret);
> > >  		return ret;
> > > @@ -1089,9 +1043,8 @@ int V4L2VideoDevice::streamOff()
> > >  {
> > >  	int ret;
> > >
> > > -	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
> > > +	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
> > >  	if (ret < 0) {
> > > -		ret = -errno;
> > >  		LOG(V4L2, Error)
> > >  			<< "Failed to stop streaming: " << strerror(-ret);
> > >  		return ret;

Patch

diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h
new file mode 100644
index 000000000000..2c26f3eae2b4
--- /dev/null
+++ b/src/libcamera/include/v4l2_device.h
@@ -0,0 +1,43 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * v4l2_device.h - Common base for V4L2 video devices and subdevices
+ */
+#ifndef __LIBCAMERA_V4L2_DEVICE_H__
+#define __LIBCAMERA_V4L2_DEVICE_H__
+
+#include <string>
+
+#include "log.h"
+
+namespace libcamera {
+
+class V4L2Device : protected Loggable
+{
+public:
+	void close();
+	bool isOpen() const { return fd_ != -1; }
+	const std::string &deviceNode() const { return deviceNode_; }
+
+protected:
+	V4L2Device(const std::string &deviceNode, const std::string &logTag);
+	~V4L2Device() {}
+
+	int fd() { return fd_; }
+
+	int open(unsigned int flags);
+
+	int ioctl(unsigned long request, void *argp);
+
+	std::string logPrefix() const { return "'" + logTag_ + "'"; }
+
+private:
+	std::string deviceNode_;
+	std::string logTag_;
+	int fd_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */
diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h
index 9afd28b6db02..b1da4a87c553 100644
--- a/src/libcamera/include/v4l2_subdevice.h
+++ b/src/libcamera/include/v4l2_subdevice.h
@@ -16,6 +16,7 @@ 
 #include "formats.h"
 #include "log.h"
 #include "media_object.h"
+#include "v4l2_device.h"
 
 namespace libcamera {
 
@@ -28,7 +29,7 @@  struct V4L2SubdeviceFormat {
 	const std::string toString() const;
 };
 
-class V4L2Subdevice : protected Loggable
+class V4L2Subdevice : public V4L2Device
 {
 public:
 	explicit V4L2Subdevice(const MediaEntity *entity);
@@ -37,8 +38,6 @@  public:
 	~V4L2Subdevice();
 
 	int open();
-	bool isOpen() const;
-	void close();
 
 	const MediaEntity *entity() const { return entity_; }
 
@@ -53,9 +52,6 @@  public:
 	static V4L2Subdevice *fromEntityName(const MediaDevice *media,
 					     const std::string &entity);
 
-protected:
-	std::string logPrefix() const;
-
 private:
 	std::vector<unsigned int> enumPadCodes(unsigned int pad);
 	std::vector<SizeRange> enumPadSizes(unsigned int pad,
@@ -65,7 +61,6 @@  private:
 			 Rectangle *rect);
 
 	const MediaEntity *entity_;
-	int fd_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
index 895658805b47..e73dec13f847 100644
--- a/src/libcamera/include/v4l2_videodevice.h
+++ b/src/libcamera/include/v4l2_videodevice.h
@@ -18,6 +18,7 @@ 
 
 #include "formats.h"
 #include "log.h"
+#include "v4l2_device.h"
 
 namespace libcamera {
 
@@ -112,7 +113,7 @@  public:
 	const std::string toString() const;
 };
 
-class V4L2VideoDevice : protected Loggable
+class V4L2VideoDevice : public V4L2Device
 {
 public:
 	explicit V4L2VideoDevice(const std::string &deviceNode);
@@ -123,13 +124,11 @@  public:
 	V4L2VideoDevice &operator=(const V4L2VideoDevice &) = 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(); }
-	const std::string &deviceNode() const { return deviceNode_; }
 
 	int getFormat(V4L2DeviceFormat *format);
 	int setFormat(V4L2DeviceFormat *format);
@@ -148,9 +147,6 @@  public:
 	static V4L2VideoDevice *fromEntityName(const MediaDevice *media,
 					       const std::string &entity);
 
-protected:
-	std::string logPrefix() const;
-
 private:
 	int getFormatMeta(V4L2DeviceFormat *format);
 	int setFormatMeta(V4L2DeviceFormat *format);
@@ -171,8 +167,6 @@  private:
 	Buffer *dequeueBuffer();
 	void bufferAvailable(EventNotifier *notifier);
 
-	std::string deviceNode_;
-	int fd_;
 	V4L2Capability caps_;
 
 	enum v4l2_buf_type bufferType_;
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 15ab53b1abbe..f26ad5b2dc57 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -23,6 +23,7 @@  libcamera_sources = files([
     'stream.cpp',
     'timer.cpp',
     'utils.cpp',
+    'v4l2_device.cpp',
     'v4l2_subdevice.cpp',
     'v4l2_videodevice.cpp',
 ])
@@ -41,6 +42,7 @@  libcamera_headers = files([
     'include/media_object.h',
     'include/pipeline_handler.h',
     'include/utils.h',
+    'include/v4l2_device.h',
     'include/v4l2_subdevice.h',
     'include/v4l2_videodevice.h',
 ])
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
new file mode 100644
index 000000000000..eeed0a70fb0e
--- /dev/null
+++ b/src/libcamera/v4l2_device.cpp
@@ -0,0 +1,144 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * v4l2_device.cpp - Common base for V4L2 video devices and subdevices
+ */
+
+#include "v4l2_device.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "log.h"
+
+/**
+ * \file v4l2_device.h
+ * \brief Common base for V4L2 devices and subdevices
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(V4L2)
+
+/**
+ * \class V4L2Device
+ * \brief Base class for V4L2VideoDevice and V4L2Subdevice
+ *
+ * The V4L2Device class groups together the methods and fields common to
+ * both the V4L2VideoDevice and V4L2Subdevice classes, and provides a base
+ * class whith methods to open and close the device node associated with the
+ * device and to perform IOCTL system calls on it.
+ *
+ * The V4L2Device class cannot be instantiated directly, as its constructor
+ * is protected. Users should instead create instances of one the derived
+ * classes to model either a V4L2 video device or a V4L2 subdevice.
+ */
+
+/**
+ * \brief Close the device node
+ *
+ * Reset the file descriptor to -1
+ */
+void V4L2Device::close()
+{
+	if (!isOpen())
+		return;
+
+	if (::close(fd_) < 0)
+		LOG(V4L2, Error) << "Failed to close V4L2 device: "
+				 << strerror(errno);
+	fd_ = -1;
+}
+
+/**
+ * \fn V4L2Device::isOpen()
+ * \brief Check if the V4L2 device node is open
+ * \return True if the V4L2 device node is open, false otherwise
+ */
+
+/**
+ * \fn V4L2Device::deviceNode()
+ * \brief Retrieve the device node path
+ * \return The device node path
+ */
+
+/**
+ * \brief Construct a V4L2Device
+ * \param[in] deviceNode The device node filesystem path
+ * \param[in] logTag The tag to prefix log messages with. Helpful to identify
+ * the device in the log output
+ *
+ * The V4L2Device constructor is protected and can only be accessed by the
+ * V4L2VideoDevice and V4L2Subdevice derived classes.
+ *
+ * Initialize the file descriptor to -1 and store the \a deviceNode to be used
+ * at open() time, and the \a logTag to prefix log messages with.
+ */
+V4L2Device::V4L2Device(const std::string &deviceNode, const std::string &logTag)
+	: deviceNode_(deviceNode), logTag_(logTag), fd_(-1)
+{
+}
+
+/**
+ * \fn V4L2Device::fd()
+ * \brief Retrieve the V4L2 device file descriptor
+ * \return The V4L2 device file descriptor, -1 if the device node is not open
+ */
+
+/**
+ * \brief Open a V4L2 device node
+ * \param[in] flags Access mode flags
+ *
+ * Open the device node path with the provided access mode \a flags and
+ * initialize the file descriptor, which was initially set to -1.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Device::open(unsigned int flags)
+{
+	if (isOpen()) {
+		LOG(V4L2, Error) << "Device already open";
+		return -EBUSY;
+	}
+
+	int ret = ::open(deviceNode_.c_str(), flags);
+	if (ret < 0) {
+		ret = -errno;
+		LOG(V4L2, Error) << "Failed to open V4L2 device: "
+				 << strerror(-ret);
+		return ret;
+	}
+
+	fd_ = ret;
+
+	return 0;
+}
+
+/**
+ * \brief Perform an IOCTL system call on the device node
+ * \param[in] request The IOCTL request code
+ * \param[in] argp A pointer to the IOCTL argument
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Device::ioctl(unsigned long request, void *argp)
+{
+	/*
+	 * Printing out an error message is usually better performed
+	 * in the caller, which can provide more context.
+	 */
+	if (::ioctl(fd_, request, argp) < 0)
+		return -errno;
+
+	return 0;
+}
+
+/**
+ * \fn V4L2Device::logPrefix()
+ * \brief Retrieve the tag to prefix log messages with
+ * \return The tag to prefix log messages with
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 48f1fcb30ed4..d0e1d717b26c 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -29,7 +29,7 @@ 
 
 namespace libcamera {
 
-LOG_DEFINE_CATEGORY(V4L2Subdev)
+LOG_DECLARE_CATEGORY(V4L2)
 
 /**
  * \struct V4L2SubdeviceFormat
@@ -102,7 +102,7 @@  const std::string V4L2SubdeviceFormat::toString() const
  * path
  */
 V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
-	: entity_(entity), fd_(-1)
+	: V4L2Device(entity->deviceNode(), entity->name()), entity_(entity)
 {
 }
 
@@ -117,45 +117,7 @@  V4L2Subdevice::~V4L2Subdevice()
  */
 int V4L2Subdevice::open()
 {
-	int ret;
-
-	if (isOpen()) {
-		LOG(V4L2Subdev, Error) << "Device already open";
-		return -EBUSY;
-	}
-
-	ret = ::open(entity_->deviceNode().c_str(), O_RDWR);
-	if (ret < 0) {
-		ret = -errno;
-		LOG(V4L2Subdev, Error)
-			<< "Failed to open V4L2 subdevice '"
-			<< entity_->deviceNode() << "': " << strerror(-ret);
-		return ret;
-	}
-	fd_ = ret;
-
-	return 0;
-}
-
-/**
- * \brief Check if the subdevice is open
- * \return True if the subdevice is open, false otherwise
- */
-bool V4L2Subdevice::isOpen() const
-{
-	return fd_ != -1;
-}
-
-/**
- * \brief Close the subdevice, releasing any resources acquired by open()
- */
-void V4L2Subdevice::close()
-{
-	if (!isOpen())
-		return;
-
-	::close(fd_);
-	fd_ = -1;
+	return V4L2Device::open(O_RDWR);
 }
 
 /**
@@ -200,7 +162,7 @@  ImageFormats V4L2Subdevice::formats(unsigned int pad)
 	ImageFormats formats;
 
 	if (pad >= entity_->pads().size()) {
-		LOG(V4L2Subdev, Error) << "Invalid pad: " << pad;
+		LOG(V4L2, Error) << "Invalid pad: " << pad;
 		return {};
 	}
 
@@ -210,7 +172,7 @@  ImageFormats V4L2Subdevice::formats(unsigned int pad)
 			return {};
 
 		if (formats.addFormat(code, sizes)) {
-			LOG(V4L2Subdev, Error)
+			LOG(V4L2, Error)
 				<< "Could not add sizes for media bus code "
 				<< code << " on pad " << pad;
 			return {};
@@ -232,10 +194,9 @@  int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format)
 	subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 	subdevFmt.pad = pad;
 
-	int ret = ioctl(fd_, VIDIOC_SUBDEV_G_FMT, &subdevFmt);
+	int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt);
 	if (ret) {
-		ret = -errno;
-		LOG(V4L2Subdev, Error)
+		LOG(V4L2, Error)
 			<< "Unable to get format on pad " << pad
 			<< ": " << strerror(-ret);
 		return ret;
@@ -268,10 +229,9 @@  int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format)
 	subdevFmt.format.height = format->size.height;
 	subdevFmt.format.code = format->mbus_code;
 
-	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_FMT, &subdevFmt);
+	int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt);
 	if (ret) {
-		ret = -errno;
-		LOG(V4L2Subdev, Error)
+		LOG(V4L2, Error)
 			<< "Unable to set format on pad " << pad
 			<< ": " << strerror(-ret);
 		return ret;
@@ -305,11 +265,6 @@  V4L2Subdevice *V4L2Subdevice::fromEntityName(const MediaDevice *media,
 	return new V4L2Subdevice(mediaEntity);
 }
 
-std::string V4L2Subdevice::logPrefix() const
-{
-	return "'" + entity_->name() + "'";
-}
-
 std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
 {
 	std::vector<unsigned int> codes;
@@ -321,18 +276,17 @@  std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad)
 		mbusEnum.index = index;
 		mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 
-		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
+		ret = ioctl(VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbusEnum);
 		if (ret)
 			break;
 
 		codes.push_back(mbusEnum.code);
 	}
 
-	if (ret && errno != EINVAL) {
-		ret = errno;
-		LOG(V4L2Subdev, Error)
+	if (ret < 0 && ret != -EINVAL) {
+		LOG(V4L2, Error)
 			<< "Unable to enumerate formats on pad " << pad
-			<< ": " << strerror(ret);
+			<< ": " << strerror(-ret);
 		return {};
 	}
 
@@ -352,7 +306,7 @@  std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
 		sizeEnum.code = code;
 		sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 
-		ret = ioctl(fd_, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
+		ret = ioctl(VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &sizeEnum);
 		if (ret)
 			break;
 
@@ -360,9 +314,8 @@  std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad,
 				   sizeEnum.max_width, sizeEnum.max_height);
 	}
 
-	if (ret && (errno != EINVAL && errno != ENOTTY)) {
-		ret = -errno;
-		LOG(V4L2Subdev, Error)
+	if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) {
+		LOG(V4L2, Error)
 			<< "Unable to enumerate sizes on pad " << pad
 			<< ": " << strerror(-ret);
 		return {};
@@ -386,10 +339,9 @@  int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target,
 	sel.r.width = rect->w;
 	sel.r.height = rect->h;
 
-	int ret = ioctl(fd_, VIDIOC_SUBDEV_S_SELECTION, &sel);
+	int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
 	if (ret < 0) {
-		ret = -errno;
-		LOG(V4L2Subdev, Error)
+		LOG(V4L2, Error)
 			<< "Unable to set rectangle " << target << " on pad "
 			<< pad << ": " << strerror(-ret);
 		return ret;
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 0e70498c8bb1..403e0b2e0653 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -30,7 +30,7 @@ 
  */
 namespace libcamera {
 
-LOG_DEFINE_CATEGORY(V4L2)
+LOG_DECLARE_CATEGORY(V4L2)
 
 /**
  * \struct V4L2Capability
@@ -270,7 +270,7 @@  const std::string V4L2DeviceFormat::toString() const
  * \param[in] deviceNode The file-system path to the video device node
  */
 V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
-	: deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr),
+	: V4L2Device(deviceNode, deviceNode), bufferPool_(nullptr),
 	  queuedBuffersCount_(0), fdEvent_(nullptr)
 {
 	/*
@@ -305,23 +305,12 @@  int V4L2VideoDevice::open()
 {
 	int ret;
 
-	if (isOpen()) {
-		LOG(V4L2, Error) << "Device already open";
-		return -EBUSY;
-	}
-
-	ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK);
-	if (ret < 0) {
-		ret = -errno;
-		LOG(V4L2, Error)
-			<< "Failed to open V4L2 device: " << strerror(-ret);
+	ret = V4L2Device::open(O_RDWR | O_NONBLOCK);
+	if (ret < 0)
 		return ret;
-	}
-	fd_ = ret;
 
-	ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_);
+	ret = ioctl(VIDIOC_QUERYCAP, &caps_);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to query device capabilities: "
 			<< strerror(-ret);
@@ -342,20 +331,20 @@  int V4L2VideoDevice::open()
 	 * (POLLIN), and write notifications for OUTPUT devices (POLLOUT).
 	 */
 	if (caps_.isVideoCapture()) {
-		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
+		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
 		bufferType_ = caps_.isMultiplanar()
 			    ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
 			    : V4L2_BUF_TYPE_VIDEO_CAPTURE;
 	} else if (caps_.isVideoOutput()) {
-		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
+		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
 		bufferType_ = caps_.isMultiplanar()
 			    ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
 			    : V4L2_BUF_TYPE_VIDEO_OUTPUT;
 	} else if (caps_.isMetaCapture()) {
-		fdEvent_ = new EventNotifier(fd_, EventNotifier::Read);
+		fdEvent_ = new EventNotifier(fd(), EventNotifier::Read);
 		bufferType_ = V4L2_BUF_TYPE_META_CAPTURE;
 	} else if (caps_.isMetaOutput()) {
-		fdEvent_ = new EventNotifier(fd_, EventNotifier::Write);
+		fdEvent_ = new EventNotifier(fd(), EventNotifier::Write);
 		bufferType_ = V4L2_BUF_TYPE_META_OUTPUT;
 	} else {
 		LOG(V4L2, Error) << "Device is not a supported type";
@@ -368,28 +357,18 @@  int V4L2VideoDevice::open()
 	return 0;
 }
 
-/**
- * \brief Check if device is successfully opened
- * \return True if the device is open, false otherwise
- */
-bool V4L2VideoDevice::isOpen() const
-{
-	return fd_ != -1;
-}
-
 /**
  * \brief Close the device, releasing any resources acquired by open()
  */
 void V4L2VideoDevice::close()
 {
-	if (fd_ < 0)
+	if (!isOpen())
 		return;
 
 	releaseBuffers();
 	delete fdEvent_;
 
-	::close(fd_);
-	fd_ = -1;
+	V4L2Device::close();
 }
 
 /**
@@ -410,17 +389,6 @@  void V4L2VideoDevice::close()
  * \return The string containing the device location
  */
 
-/**
- * \fn V4L2VideoDevice::deviceNode()
- * \brief Retrieve the video device node path
- * \return The video device device node path
- */
-
-std::string V4L2VideoDevice::logPrefix() const
-{
-	return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]");
-}
-
 /**
  * \brief Retrieve the image format set on the V4L2 device
  * \param[out] format The image format applied on the device
@@ -462,9 +430,8 @@  int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format)
 	int ret;
 
 	v4l2Format.type = bufferType_;
-	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
 		return ret;
 	}
@@ -488,9 +455,8 @@  int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format)
 	v4l2Format.type = bufferType_;
 	pix->dataformat = format->fourcc;
 	pix->buffersize = format->planes[0].size;
-	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
 		return ret;
 	}
@@ -516,9 +482,8 @@  int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format)
 	int ret;
 
 	v4l2Format.type = bufferType_;
-	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
 		return ret;
 	}
@@ -554,9 +519,8 @@  int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format)
 		pix->plane_fmt[i].sizeimage = format->planes[i].size;
 	}
 
-	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
 		return ret;
 	}
@@ -584,9 +548,8 @@  int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format)
 	int ret;
 
 	v4l2Format.type = bufferType_;
-	ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_G_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret);
 		return ret;
 	}
@@ -613,9 +576,8 @@  int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format)
 	pix->pixelformat = format->fourcc;
 	pix->bytesperline = format->planes[0].bpl;
 	pix->field = V4L2_FIELD_NONE;
-	ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format);
+	ret = ioctl(VIDIOC_S_FMT, &v4l2Format);
 	if (ret) {
-		ret = -errno;
 		LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret);
 		return ret;
 	}
@@ -670,9 +632,8 @@  int V4L2VideoDevice::requestBuffers(unsigned int count)
 	rb.type = bufferType_;
 	rb.memory = memoryType_;
 
-	ret = ioctl(fd_, VIDIOC_REQBUFS, &rb);
+	ret = ioctl(VIDIOC_REQBUFS, &rb);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Unable to request " << count << " buffers: "
 			<< strerror(-ret);
@@ -722,9 +683,8 @@  int V4L2VideoDevice::exportBuffers(BufferPool *pool)
 		buf.length = VIDEO_MAX_PLANES;
 		buf.m.planes = planes;
 
-		ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf);
+		ret = ioctl(VIDIOC_QUERYBUF, &buf);
 		if (ret < 0) {
-			ret = -errno;
 			LOG(V4L2, Error)
 				<< "Unable to query buffer " << i << ": "
 				<< strerror(-ret);
@@ -775,9 +735,8 @@  int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
 	expbuf.plane = planeIndex;
 	expbuf.flags = O_RDWR;
 
-	ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf);
+	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to export buffer: " << strerror(-ret);
 		return ret;
@@ -801,15 +760,14 @@  std::vector<unsigned int> V4L2VideoDevice::enumPixelformats()
 		pixelformatEnum.index = index;
 		pixelformatEnum.type = bufferType_;
 
-		ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum);
+		ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum);
 		if (ret)
 			break;
 
 		formats.push_back(pixelformatEnum.pixelformat);
 	}
 
-	if (ret && errno != EINVAL) {
-		ret = -errno;
+	if (ret && ret != -EINVAL) {
 		LOG(V4L2, Error)
 			<< "Unable to enumerate pixel formats: "
 			<< strerror(-ret);
@@ -829,7 +787,7 @@  std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
 		frameSize.index = index;
 		frameSize.pixel_format = pixelFormat;
 
-		ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize);
+		ret = ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize);
 		if (ret)
 			break;
 
@@ -867,8 +825,7 @@  std::vector<SizeRange> V4L2VideoDevice::enumSizes(unsigned int pixelFormat)
 		}
 	}
 
-	if (ret && errno != EINVAL) {
-		ret = -errno;
+	if (ret && ret != -EINVAL) {
 		LOG(V4L2, Error)
 			<< "Unable to enumerate frame sizes: "
 			<< strerror(-ret);
@@ -969,9 +926,8 @@  int V4L2VideoDevice::queueBuffer(Buffer *buffer)
 
 	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
 
-	ret = ioctl(fd_, VIDIOC_QBUF, &buf);
+	ret = ioctl(VIDIOC_QBUF, &buf);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to queue buffer " << buf.index << ": "
 			<< strerror(-ret);
@@ -1006,9 +962,8 @@  Buffer *V4L2VideoDevice::dequeueBuffer()
 		buf.m.planes = planes;
 	}
 
-	ret = ioctl(fd_, VIDIOC_DQBUF, &buf);
+	ret = ioctl(VIDIOC_DQBUF, &buf);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to dequeue buffer: " << strerror(-ret);
 		return nullptr;
@@ -1066,9 +1021,8 @@  int V4L2VideoDevice::streamOn()
 {
 	int ret;
 
-	ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_);
+	ret = ioctl(VIDIOC_STREAMON, &bufferType_);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to start streaming: " << strerror(-ret);
 		return ret;
@@ -1089,9 +1043,8 @@  int V4L2VideoDevice::streamOff()
 {
 	int ret;
 
-	ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_);
+	ret = ioctl(VIDIOC_STREAMOFF, &bufferType_);
 	if (ret < 0) {
-		ret = -errno;
 		LOG(V4L2, Error)
 			<< "Failed to stop streaming: " << strerror(-ret);
 		return ret;