[libcamera-devel,4/6] libcamera: v4l2_base: Add V4L2 control support

Message ID 20190602130435.18780-5-jacopo@jmondi.org
State Superseded
Delegated to: Jacopo Mondi
Headers show
Series
  • libcamera: v4l2_controls: Add support for V4L2 controls
Related show

Commit Message

Jacopo Mondi June 2, 2019, 1:04 p.m. UTC
Implement operations to set and get V4L2 controls in the V4L2Base class.
The operations allow to set and get a single or a list of controls.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 src/libcamera/include/v4l2_base.h |  12 ++
 src/libcamera/v4l2_base.cpp       | 290 ++++++++++++++++++++++++++++++
 2 files changed, 302 insertions(+)

Comments

Kieran Bingham June 3, 2019, 12:32 p.m. UTC | #1
Hi Jacopo,

Some thoughts out loud inline....

On 02/06/2019 14:04, Jacopo Mondi wrote:
> Implement operations to set and get V4L2 controls in the V4L2Base class.
> The operations allow to set and get a single or a list of controls.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  src/libcamera/include/v4l2_base.h |  12 ++
>  src/libcamera/v4l2_base.cpp       | 290 ++++++++++++++++++++++++++++++
>  2 files changed, 302 insertions(+)
> 
> diff --git a/src/libcamera/include/v4l2_base.h b/src/libcamera/include/v4l2_base.h
> index 2fda81a960d2..7d4e238dadfa 100644
> --- a/src/libcamera/include/v4l2_base.h
> +++ b/src/libcamera/include/v4l2_base.h
> @@ -9,6 +9,8 @@
>  
>  #include <vector>
>  
> +#include <v4l2_controls.h>
> +
>  namespace libcamera {
>  
>  class V4L2Base
> @@ -22,8 +24,18 @@ public:
>  	virtual void close() = 0;
>  	bool isOpen() const;
>  
> +	std::vector<V4L2Control *> getControls(std::vector<unsigned int> &ctrl_ids);

In my controls, I pass a list <well a map, or a set; I have two
implementations currently> of the actual ControlValue objects, which get
filled in.

I wonder if there will be much differences between the two interface
designs.

> +	V4L2Control *getControl(unsigned int ctrl_id);
> +	int setControls(std::vector<V4L2Control *> &ctrls);
> +	int setControl(V4L2Control *ctrl);

I think I'd group those as getControls setControls, and then getControl,
setControl, so that they are grouped by 'functionality' but that might
not be a preferred sorting... so either way.



> +
>  protected:
>  	int fd_;
> +
> +private:
> +	int queryControl(unsigned int ctrl_id,
> +			 struct v4l2_query_ext_ctrl *qext_ctrl);
> +
>  };
>  
>  }; /* namespace libcamera */
> diff --git a/src/libcamera/v4l2_base.cpp b/src/libcamera/v4l2_base.cpp
> index 7d05a3c82e4d..423a4d84e276 100644
> --- a/src/libcamera/v4l2_base.cpp
> +++ b/src/libcamera/v4l2_base.cpp
> @@ -5,7 +5,13 @@
>   * v4l2_base.cpp - Common base for V4L2 devices and subdevices
>   */
>  
> +#include <linux/v4l2-subdev.h>
> +#include <linux/videodev2.h>
> +#include <sys/ioctl.h>
> +
> +#include "log.h"
>  #include "v4l2_base.h"
> +#include "v4l2_controls.h"
>  
>  /**
>   * \file v4l2_base.h
> @@ -14,6 +20,8 @@
>  
>  namespace libcamera {
>  
> +LOG_DEFINE_CATEGORY(V4L2Base)
> +
>  /**
>   * \class V4L2Base
>   * \brief Base class for V4L2Device and V4L2Subdevice
> @@ -31,6 +39,7 @@ namespace libcamera {
>   */
>  
>  /**
> + * \fn V4L2Base::open()
>   * \brief Open a V4L2 device or subdevice
>   *
>   * Pure virtual method to be implemented by the derived classes.
> @@ -39,6 +48,7 @@ namespace libcamera {
>   */
>  
>  /**
> + * \fn V4L2Base::close()
>   * \brief Close the device or subdevice
>   *
>   * Pure virtual method to be implemented by the derived classes.
> @@ -53,6 +63,286 @@ bool V4L2Base::isOpen() const
>  	return fd_ != -1;
>  }
>  
> +int V4L2Base::queryControl(unsigned int id,
> +			   struct v4l2_query_ext_ctrl *qext_ctrl)
> +{
> +	qext_ctrl->id = id;
> +
> +	int ret = ioctl(fd_, VIDIOC_QUERY_EXT_CTRL, qext_ctrl);
> +	if (ret) {
> +		ret = -errno;
> +		LOG(V4L2Base, Error)
> +			<< "Unable to query extended control 0x"
> +			<< std::hex << qext_ctrl->id
> +			<< ": " << strerror(-ret);
> +		return ret;
> +	}
> +
> +	if (qext_ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
> +		LOG(V4L2Base, Error)
> +			<< "Extended control 0x" << std::hex
> +			<< qext_ctrl->id << " is disabled";
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * \brief Get values of a list of control IDs
> + * \param ctrl_ids The list of control IDs to retrieve value of
> + * \return A list of V4L2Control on success or a empty list otherwise
> + */
> +std::vector<V4L2Control *> V4L2Base::getControls(std::vector<unsigned int> &ctrl_ids)
> +{
> +	unsigned int count = ctrl_ids.size();
> +	if (!count)
> +		return std::vector<V4L2Control *> {};
> +
> +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> +					      (new struct v4l2_ext_control[count]);
> +	if (!v4l2_ctrls)
> +		return std::vector<V4L2Control *> {};
> +
> +	/*
> +	 * Query each control in the vector to get back its type and expected
> +	 * size in order to get its current value.
> +	 */
> +	for (unsigned int i = 0; i < count; ++i) {
> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> +		unsigned int ctrl_id = ctrl_ids[i];
> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> +
> +		int ret = queryControl(ctrl_id, &qext_ctrl);
> +		if (ret)
> +			return std::vector<V4L2Control *> {};
> +
> +		v4l2_ctrl->id = ctrl_id;
> +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> +
> +		/* Reserve memory for controls with payload. */
> +		if (!(qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
> +			continue;
> +
> +		switch (qext_ctrl.type) {
> +		case V4L2_CTRL_TYPE_U8:
> +			v4l2_ctrl->p_u8 = static_cast<uint8_t *>
> +					  (new uint8_t[v4l2_ctrl->size]);
> +			break;
> +		case V4L2_CTRL_TYPE_U16:
> +			v4l2_ctrl->p_u16 = static_cast<uint16_t *>
> +					  (new uint16_t[v4l2_ctrl->size]);
> +			break;
> +		case V4L2_CTRL_TYPE_U32:
> +			v4l2_ctrl->p_u32 = static_cast<uint32_t *>
> +					  (new uint32_t[v4l2_ctrl->size]);
> +			break;
> +		}
> +	}
> +
> +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> +	v4l2_ext_ctrls.count = count;
> +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> +
> +	int ret = ioctl(fd_, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls);
> +	if (ret) {
> +		ret = -errno;
> +		LOG(V4L2Base, Error)
> +			<< "Unable to get extended controls: " << strerror(-ret);
> +			return std::vector<V4L2Control *> {};
> +	}
> +
> +	/*
> +	 * For each requested control, create a V4L2Control which contains
> +	 * the current value and store it in the vector returned to the caller.
> +	 */
> +	std::vector<V4L2Control *> controls = {};
> +	for (unsigned int i = 0; i < count; ++i) {
> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> +
> +		int ret = queryControl(v4l2_ctrl->id, &qext_ctrl);
> +		if (ret)
> +			return std::vector<V4L2Control *> {};
> +
> +		unsigned int size = qext_ctrl.elem_size * qext_ctrl.elem_size;
> +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> +			/* Control types with payload */
> +			switch (qext_ctrl.type) {
> +			case V4L2_CTRL_TYPE_U8:
> +			{
> +				V4L2U8Control *c =
> +					new V4L2U8Control(v4l2_ctrl->id, size,
> +							  v4l2_ctrl->p_u8);
> +				controls.push_back(c);
> +			}
> +				break;
> +			case V4L2_CTRL_TYPE_U16:
> +			{
> +				V4L2U16Control *c =
> +					new V4L2U16Control(v4l2_ctrl->id, size,
> +							   v4l2_ctrl->p_u16);
> +				controls.push_back(c);
> +			}
> +				break;
> +			case V4L2_CTRL_TYPE_U32:
> +			{
> +				V4L2U32Control *c =
> +					new V4L2U32Control(v4l2_ctrl->id, size,
> +							  v4l2_ctrl->p_u32);
> +				controls.push_back(c);
> +			}
> +				break;
> +			default:
> +				LOG(V4L2Base, Error)
> +					<< "Compound control types not supported: "
> +					<< std::hex << qext_ctrl.type;
> +				return std::vector<V4L2Control *> {};
> +			}
> +		} else {
> +			/* Control types without payload. */
> +			switch (qext_ctrl.type) {
> +			case V4L2_CTRL_TYPE_INTEGER64:
> +			{
> +				V4L2Int64Control *c =
> +					new V4L2Int64Control(v4l2_ctrl->id,
> +							     v4l2_ctrl->value64);
> +				controls.push_back(c);

I think this could be a
	controls.emplace(V4L2Int64Control(v4l2_ctrl->id,
					  v4l2_ctrl->value64);

if you prefer that style ...

Aha - except you have a vector of pointers, rather than a vector of
objects ...



> +			}
> +				break;
> +			case V4L2_CTRL_TYPE_INTEGER:
> +			case V4L2_CTRL_TYPE_BOOLEAN:
> +			case V4L2_CTRL_TYPE_MENU:
> +			case V4L2_CTRL_TYPE_BUTTON:
> +			{
> +				V4L2IntControl *c =
> +					new V4L2IntControl(v4l2_ctrl->id,
> +							   v4l2_ctrl->value);
> +				controls.push_back(c);
> +			}
> +				break;
> +			default:
> +				LOG(V4L2Base, Error)
> +					<< "Invalid non-compound control type :"
> +					<< std::hex << qext_ctrl.type;
> +				return std::vector<V4L2Control *> {};
> +			}
> +		}
> +	}
> +
> +	return controls;
> +}
> +
> +/**
> + * \brief Get the value of control with id \a ctrl_id
> + * \param ctrl_id The control id
> + * \return The V4L2Control which represent the value of the control on success
> + * nullptr otherwise
> + */
> +V4L2Control *V4L2Base::getControl(unsigned int ctrl_id)
> +{
> +	std::vector<unsigned int> v = { ctrl_id, };
> +	std::vector<V4L2Control *> ctrls = getControls(v);
> +
> +	if (ctrls.empty())
> +		return nullptr;
> +
> +	return ctrls[0];
> +}
> +
> +/**
> + * \brief Apply a list of controls to the device
> + * \param ctrls The list of controls to apply
> + * \return 0 on success or a negative error code otherwise
> + */
> +int V4L2Base::setControls(std::vector<V4L2Control *> &ctrls)
> +{
> +	unsigned int count = ctrls.size();
> +	if (!count)
> +		return -EINVAL;
> +
> +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> +					      (new struct v4l2_ext_control[count]);
> +	if (!v4l2_ctrls)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Query each control in the vector to get back its type and expected
> +	 * size and set the new desired value in each v4l2_ext_control member.
> +	 */
> +	for (unsigned int i = 0; i < count; ++i) {
> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> +		V4L2Control *ctrl = ctrls[i];
> +
> +		int ret = queryControl(ctrl->id(), &qext_ctrl);
> +		if (ret)
> +			return ret;
> +
> +		v4l2_ctrl->id = ctrl->id();
> +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> +
> +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> +			/** \todo: Support set controls with payload. */
> +			LOG(V4L2Base, Error)
> +				<< "Controls with payload not supported";
> +			return -EINVAL;
> +		} else {
> +			switch (qext_ctrl.type) {
> +			case V4L2_CTRL_TYPE_INTEGER64:
> +			{
> +				V4L2Int64Control *c =
> +					static_cast<V4L2Int64Control *>(ctrl);


It's these static_casts that feel a bit horrible to me. In my
implementation there is just single class, so there is no casting necessary,

> +				v4l2_ctrl->value64 = c->value();

However, this would be c->getInteger64();


> +			}
> +				break;
> +			case V4L2_CTRL_TYPE_INTEGER:
> +			case V4L2_CTRL_TYPE_BOOLEAN:
> +			case V4L2_CTRL_TYPE_MENU:
> +			case V4L2_CTRL_TYPE_BUTTON:
> +			{
> +				V4L2IntControl *c =
> +					static_cast<V4L2IntControl *>(ctrl);
> +				v4l2_ctrl->value = c->value();

and getInteger(); (although getBoolean() can be separated as well);

As we know the types in this switch, I wonder if there is much
difference int taste between the two approaches...


> +			}
> +				break;
> +			default:
> +				LOG(V4L2Base, Error)
> +					<< "Invalid non-compound control type :"
> +					<< qext_ctrl.type;
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> +	v4l2_ext_ctrls.count = count;
> +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> +
> +	int ret = ioctl(fd_, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls);
> +	if (ret) {
> +		ret = -errno;
> +		LOG(V4L2Base, Error)
> +			<< "Unable to set extended controls: " << strerror(-ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * \brief Apply the control \a ctrl to the device
> + * \param ctrl The control to apply to the device
> + * \return 0 on success, a negative error code otherwise
> + */
> +int V4L2Base::setControl(V4L2Control *ctrl)
> +{
> +	std::vector<V4L2Control *> v = { ctrl, };
> +	return setControls(v);
> +}
> +
>  /**
>   * \var V4L2Base::fd_
>   * \brief The V4L2 device or subdevice device node file descriptor
>
Laurent Pinchart June 8, 2019, 4:07 p.m. UTC | #2
Hi Jacopo,

Thank you for the patch.

On Mon, Jun 03, 2019 at 01:32:58PM +0100, Kieran Bingham wrote:
> Hi Jacopo,
> 
> Some thoughts out loud inline....
> 
> On 02/06/2019 14:04, Jacopo Mondi wrote:
> > Implement operations to set and get V4L2 controls in the V4L2Base class.
> > The operations allow to set and get a single or a list of controls.
> > 
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > ---
> >  src/libcamera/include/v4l2_base.h |  12 ++
> >  src/libcamera/v4l2_base.cpp       | 290 ++++++++++++++++++++++++++++++
> >  2 files changed, 302 insertions(+)
> > 
> > diff --git a/src/libcamera/include/v4l2_base.h b/src/libcamera/include/v4l2_base.h
> > index 2fda81a960d2..7d4e238dadfa 100644
> > --- a/src/libcamera/include/v4l2_base.h
> > +++ b/src/libcamera/include/v4l2_base.h
> > @@ -9,6 +9,8 @@
> >  
> >  #include <vector>
> >  
> > +#include <v4l2_controls.h>

This should be "v4l2_controls.h", but you can simply forward-declare the
V4L2Control class instead.

> > +
> >  namespace libcamera {
> >  
> >  class V4L2Base
> > @@ -22,8 +24,18 @@ public:
> >  	virtual void close() = 0;
> >  	bool isOpen() const;
> >  
> > +	std::vector<V4L2Control *> getControls(std::vector<unsigned int> &ctrl_ids);

A vector of pointer isn't very nice as it requires the caller to delete
each item. I would prefer passing a reference to a vector (or map) of
values to the function, and having it fill the values.

> In my controls, I pass a list <well a map, or a set; I have two
> implementations currently> of the actual ControlValue objects, which get
> filled in.
>
> I wonder if there will be much differences between the two interface
> designs.

One big difference is that a vector is ordered while a map or set isn't.
The order in which controls are set can make a difference.

> > +	V4L2Control *getControl(unsigned int ctrl_id);
> > +	int setControls(std::vector<V4L2Control *> &ctrls);
> > +	int setControl(V4L2Control *ctrl);
> 
> I think I'd group those as getControls setControls, and then getControl,
> setControl, so that they are grouped by 'functionality' but that might
> not be a preferred sorting... so either way.

I would prefer that as well.

> > +
> >  protected:
> >  	int fd_;
> > +
> > +private:
> > +	int queryControl(unsigned int ctrl_id,
> > +			 struct v4l2_query_ext_ctrl *qext_ctrl);
> > +
> >  };
> >  
> >  }; /* namespace libcamera */
> > diff --git a/src/libcamera/v4l2_base.cpp b/src/libcamera/v4l2_base.cpp
> > index 7d05a3c82e4d..423a4d84e276 100644
> > --- a/src/libcamera/v4l2_base.cpp
> > +++ b/src/libcamera/v4l2_base.cpp
> > @@ -5,7 +5,13 @@
> >   * v4l2_base.cpp - Common base for V4L2 devices and subdevices
> >   */
> >  
> > +#include <linux/v4l2-subdev.h>
> > +#include <linux/videodev2.h>
> > +#include <sys/ioctl.h>
> > +
> > +#include "log.h"
> >  #include "v4l2_base.h"

The issue comes from a previous patch, but v4l2_base.h should be the
very first header included, to help ensuring it is self-contained.

> > +#include "v4l2_controls.h"
> >  
> >  /**
> >   * \file v4l2_base.h
> > @@ -14,6 +20,8 @@
> >  
> >  namespace libcamera {
> >  
> > +LOG_DEFINE_CATEGORY(V4L2Base)
> > +
> >  /**
> >   * \class V4L2Base
> >   * \brief Base class for V4L2Device and V4L2Subdevice
> > @@ -31,6 +39,7 @@ namespace libcamera {
> >   */
> >  
> >  /**
> > + * \fn V4L2Base::open()

Does this belong to this patch ? Same for the next method.

> >   * \brief Open a V4L2 device or subdevice
> >   *
> >   * Pure virtual method to be implemented by the derived classes.
> > @@ -39,6 +48,7 @@ namespace libcamera {
> >   */
> >  
> >  /**
> > + * \fn V4L2Base::close()
> >   * \brief Close the device or subdevice
> >   *
> >   * Pure virtual method to be implemented by the derived classes.
> > @@ -53,6 +63,286 @@ bool V4L2Base::isOpen() const
> >  	return fd_ != -1;
> >  }
> >  
> > +int V4L2Base::queryControl(unsigned int id,
> > +			   struct v4l2_query_ext_ctrl *qext_ctrl)
> > +{
> > +	qext_ctrl->id = id;
> > +
> > +	int ret = ioctl(fd_, VIDIOC_QUERY_EXT_CTRL, qext_ctrl);
> > +	if (ret) {
> > +		ret = -errno;
> > +		LOG(V4L2Base, Error)
> > +			<< "Unable to query extended control 0x"

s/extended //

> > +			<< std::hex << qext_ctrl->id
> > +			<< ": " << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	if (qext_ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
> > +		LOG(V4L2Base, Error)
> > +			<< "Extended control 0x" << std::hex
> > +			<< qext_ctrl->id << " is disabled";
> > +		return -EINVAL;
> > +	}

I would handle this in the caller, it's not a queryControl() error as
such.

> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \brief Get values of a list of control IDs
> > + * \param ctrl_ids The list of control IDs to retrieve value of
> > + * \return A list of V4L2Control on success or a empty list otherwise
> > + */
> > +std::vector<V4L2Control *> V4L2Base::getControls(std::vector<unsigned int> &ctrl_ids)
> > +{
> > +	unsigned int count = ctrl_ids.size();
> > +	if (!count)
> > +		return std::vector<V4L2Control *> {};
> > +
> > +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> > +					      (new struct v4l2_ext_control[count]);
> > +	if (!v4l2_ctrls)
> > +		return std::vector<V4L2Control *> {};
> > +
> > +	/*
> > +	 * Query each control in the vector to get back its type and expected
> > +	 * size in order to get its current value.
> > +	 */
> > +	for (unsigned int i = 0; i < count; ++i) {
> > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > +		unsigned int ctrl_id = ctrl_ids[i];
> > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > +
> > +		int ret = queryControl(ctrl_id, &qext_ctrl);
> > +		if (ret)
> > +			return std::vector<V4L2Control *> {};
> > +
> > +		v4l2_ctrl->id = ctrl_id;
> > +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> > +
> > +		/* Reserve memory for controls with payload. */
> > +		if (!(qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
> > +			continue;
> > +
> > +		switch (qext_ctrl.type) {
> > +		case V4L2_CTRL_TYPE_U8:
> > +			v4l2_ctrl->p_u8 = static_cast<uint8_t *>
> > +					  (new uint8_t[v4l2_ctrl->size]);
> > +			break;
> > +		case V4L2_CTRL_TYPE_U16:
> > +			v4l2_ctrl->p_u16 = static_cast<uint16_t *>
> > +					  (new uint16_t[v4l2_ctrl->size]);
> > +			break;
> > +		case V4L2_CTRL_TYPE_U32:
> > +			v4l2_ctrl->p_u32 = static_cast<uint32_t *>
> > +					  (new uint32_t[v4l2_ctrl->size]);
> > +			break;
> > +		}
> > +	}
> > +
> > +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> > +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> > +	v4l2_ext_ctrls.count = count;
> > +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> > +
> > +	int ret = ioctl(fd_, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls);
> > +	if (ret) {
> > +		ret = -errno;
> > +		LOG(V4L2Base, Error)
> > +			<< "Unable to get extended controls: " << strerror(-ret);
> > +			return std::vector<V4L2Control *> {};
> > +	}
> > +
> > +	/*
> > +	 * For each requested control, create a V4L2Control which contains
> > +	 * the current value and store it in the vector returned to the caller.
> > +	 */
> > +	std::vector<V4L2Control *> controls = {};
> > +	for (unsigned int i = 0; i < count; ++i) {
> > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > +
> > +		int ret = queryControl(v4l2_ctrl->id, &qext_ctrl);
> > +		if (ret)
> > +			return std::vector<V4L2Control *> {};
> > +
> > +		unsigned int size = qext_ctrl.elem_size * qext_ctrl.elem_size;
> > +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> > +			/* Control types with payload */
> > +			switch (qext_ctrl.type) {
> > +			case V4L2_CTRL_TYPE_U8:
> > +			{
> > +				V4L2U8Control *c =
> > +					new V4L2U8Control(v4l2_ctrl->id, size,
> > +							  v4l2_ctrl->p_u8);
> > +				controls.push_back(c);
> > +			}
> > +				break;
> > +			case V4L2_CTRL_TYPE_U16:
> > +			{
> > +				V4L2U16Control *c =
> > +					new V4L2U16Control(v4l2_ctrl->id, size,
> > +							   v4l2_ctrl->p_u16);
> > +				controls.push_back(c);
> > +			}
> > +				break;
> > +			case V4L2_CTRL_TYPE_U32:
> > +			{
> > +				V4L2U32Control *c =
> > +					new V4L2U32Control(v4l2_ctrl->id, size,
> > +							  v4l2_ctrl->p_u32);
> > +				controls.push_back(c);
> > +			}
> > +				break;
> > +			default:
> > +				LOG(V4L2Base, Error)
> > +					<< "Compound control types not supported: "
> > +					<< std::hex << qext_ctrl.type;
> > +				return std::vector<V4L2Control *> {};
> > +			}
> > +		} else {
> > +			/* Control types without payload. */
> > +			switch (qext_ctrl.type) {
> > +			case V4L2_CTRL_TYPE_INTEGER64:
> > +			{
> > +				V4L2Int64Control *c =
> > +					new V4L2Int64Control(v4l2_ctrl->id,
> > +							     v4l2_ctrl->value64);
> > +				controls.push_back(c);
> 
> I think this could be a
> 	controls.emplace(V4L2Int64Control(v4l2_ctrl->id,
> 					  v4l2_ctrl->value64);
> 
> if you prefer that style ...
> 
> Aha - except you have a vector of pointers, rather than a vector of
> objects ...
> 
> > +			}
> > +				break;
> > +			case V4L2_CTRL_TYPE_INTEGER:
> > +			case V4L2_CTRL_TYPE_BOOLEAN:
> > +			case V4L2_CTRL_TYPE_MENU:
> > +			case V4L2_CTRL_TYPE_BUTTON:
> > +			{
> > +				V4L2IntControl *c =
> > +					new V4L2IntControl(v4l2_ctrl->id,
> > +							   v4l2_ctrl->value);
> > +				controls.push_back(c);
> > +			}
> > +				break;
> > +			default:
> > +				LOG(V4L2Base, Error)
> > +					<< "Invalid non-compound control type :"
> > +					<< std::hex << qext_ctrl.type;
> > +				return std::vector<V4L2Control *> {};
> > +			}
> > +		}
> > +	}
> > +
> > +	return controls;
> > +}
> > +
> > +/**
> > + * \brief Get the value of control with id \a ctrl_id
> > + * \param ctrl_id The control id
> > + * \return The V4L2Control which represent the value of the control on success
> > + * nullptr otherwise
> > + */
> > +V4L2Control *V4L2Base::getControl(unsigned int ctrl_id)
> > +{
> > +	std::vector<unsigned int> v = { ctrl_id, };
> > +	std::vector<V4L2Control *> ctrls = getControls(v);
> > +
> > +	if (ctrls.empty())
> > +		return nullptr;
> > +
> > +	return ctrls[0];
> > +}
> > +
> > +/**
> > + * \brief Apply a list of controls to the device
> > + * \param ctrls The list of controls to apply
> > + * \return 0 on success or a negative error code otherwise
> > + */
> > +int V4L2Base::setControls(std::vector<V4L2Control *> &ctrls)
> > +{
> > +	unsigned int count = ctrls.size();

size_t instead of unsigned int ?

> > +	if (!count)
> > +		return -EINVAL;
> > +
> > +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> > +					      (new struct v4l2_ext_control[count]);

Do you need the cast ?

> > +	if (!v4l2_ctrls)
> > +		return -ENOMEM;

new() will throw an exception if it can't construct the object, so
there's no need to check the return value.

> > +
> > +	/*
> > +	 * Query each control in the vector to get back its type and expected
> > +	 * size and set the new desired value in each v4l2_ext_control member.
> > +	 */
> > +	for (unsigned int i = 0; i < count; ++i) {
> > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > +		V4L2Control *ctrl = ctrls[i];
> > +
> > +		int ret = queryControl(ctrl->id(), &qext_ctrl);
> > +		if (ret)
> > +			return ret;

It's annoying to have to query each control every time. We should either
enumerate all of them when opening the device, or query them at runtime
but cache the control information.

> > +
> > +		v4l2_ctrl->id = ctrl->id();
> > +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> > +
> > +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> > +			/** \todo: Support set controls with payload. */
> > +			LOG(V4L2Base, Error)
> > +				<< "Controls with payload not supported";
> > +			return -EINVAL;
> > +		} else {
> > +			switch (qext_ctrl.type) {
> > +			case V4L2_CTRL_TYPE_INTEGER64:
> > +			{
> > +				V4L2Int64Control *c =
> > +					static_cast<V4L2Int64Control *>(ctrl);
> 
> It's these static_casts that feel a bit horrible to me. In my
> implementation there is just single class, so there is no casting necessary,

I don't like the casts much either, especially given that you cast based
on the type returned by the kernel, which could possibly not match the
type of the control as set by the application. This would likely lead to
memory corruption and crashes.

> > +				v4l2_ctrl->value64 = c->value();
> 
> However, this would be c->getInteger64();
> 
> > +			}
> > +				break;
> > +			case V4L2_CTRL_TYPE_INTEGER:
> > +			case V4L2_CTRL_TYPE_BOOLEAN:
> > +			case V4L2_CTRL_TYPE_MENU:
> > +			case V4L2_CTRL_TYPE_BUTTON:
> > +			{
> > +				V4L2IntControl *c =
> > +					static_cast<V4L2IntControl *>(ctrl);
> > +				v4l2_ctrl->value = c->value();
> 
> and getInteger(); (although getBoolean() can be separated as well);
> 
> As we know the types in this switch, I wonder if there is much
> difference int taste between the two approaches...
> 
> > +			}
> > +				break;
> > +			default:
> > +				LOG(V4L2Base, Error)
> > +					<< "Invalid non-compound control type :"
> > +					<< qext_ctrl.type;
> > +				return -EINVAL;
> > +			}
> > +		}
> > +	}
> > +
> > +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> > +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> > +	v4l2_ext_ctrls.count = count;
> > +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> > +
> > +	int ret = ioctl(fd_, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls);
> > +	if (ret) {
> > +		ret = -errno;
> > +		LOG(V4L2Base, Error)
> > +			<< "Unable to set extended controls: " << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * \brief Apply the control \a ctrl to the device
> > + * \param ctrl The control to apply to the device
> > + * \return 0 on success, a negative error code otherwise
> > + */
> > +int V4L2Base::setControl(V4L2Control *ctrl)
> > +{
> > +	std::vector<V4L2Control *> v = { ctrl, };
> > +	return setControls(v);
> > +}
> > +
> >  /**
> >   * \var V4L2Base::fd_
> >   * \brief The V4L2 device or subdevice device node file descriptor
Jacopo Mondi June 10, 2019, 7:38 a.m. UTC | #3
Hi Kieran, Laurent,

On Sat, Jun 08, 2019 at 07:07:08PM +0300, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> On Mon, Jun 03, 2019 at 01:32:58PM +0100, Kieran Bingham wrote:
> > Hi Jacopo,
> >
> > Some thoughts out loud inline....
> >
> > On 02/06/2019 14:04, Jacopo Mondi wrote:
> > > Implement operations to set and get V4L2 controls in the V4L2Base class.
> > > The operations allow to set and get a single or a list of controls.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > > ---
> > >  src/libcamera/include/v4l2_base.h |  12 ++
> > >  src/libcamera/v4l2_base.cpp       | 290 ++++++++++++++++++++++++++++++
> > >  2 files changed, 302 insertions(+)
> > >
> > > diff --git a/src/libcamera/include/v4l2_base.h b/src/libcamera/include/v4l2_base.h
> > > index 2fda81a960d2..7d4e238dadfa 100644
> > > --- a/src/libcamera/include/v4l2_base.h
> > > +++ b/src/libcamera/include/v4l2_base.h
> > > @@ -9,6 +9,8 @@
> > >
> > >  #include <vector>
> > >
> > > +#include <v4l2_controls.h>
>
> This should be "v4l2_controls.h", but you can simply forward-declare the
> V4L2Control class instead.
>
> > > +
> > >  namespace libcamera {
> > >
> > >  class V4L2Base
> > > @@ -22,8 +24,18 @@ public:
> > >  	virtual void close() = 0;
> > >  	bool isOpen() const;
> > >
> > > +	std::vector<V4L2Control *> getControls(std::vector<unsigned int> &ctrl_ids);
>
> A vector of pointer isn't very nice as it requires the caller to delete
> each item. I would prefer passing a reference to a vector (or map) of
> values to the function, and having it fill the values.
>

I thought about it, but how would you define the state of a Control ?
Would you create a control with id but without any associated value ?
And what if you call value() on it? I fear keeping track of that is a
pain, and I preferred to have controls that once created always have a
value associated.

I can reconsider it though.

> > In my controls, I pass a list <well a map, or a set; I have two
> > implementations currently> of the actual ControlValue objects, which get
> > filled in.
> >
> > I wonder if there will be much differences between the two interface
> > designs.
>
> One big difference is that a vector is ordered while a map or set isn't.
> The order in which controls are set can make a difference.
>

I think Kieran referred to the "container of controls to fill" vs the
"get control ids and return a container of controls" different
approaches.

> > > +	V4L2Control *getControl(unsigned int ctrl_id);
> > > +	int setControls(std::vector<V4L2Control *> &ctrls);
> > > +	int setControl(V4L2Control *ctrl);
> >
> > I think I'd group those as getControls setControls, and then getControl,
> > setControl, so that they are grouped by 'functionality' but that might
> > not be a preferred sorting... so either way.
>
> I would prefer that as well.
>

Yup.

> > > +
> > >  protected:
> > >  	int fd_;
> > > +
> > > +private:
> > > +	int queryControl(unsigned int ctrl_id,
> > > +			 struct v4l2_query_ext_ctrl *qext_ctrl);
> > > +
> > >  };
> > >
> > >  }; /* namespace libcamera */
> > > diff --git a/src/libcamera/v4l2_base.cpp b/src/libcamera/v4l2_base.cpp
> > > index 7d05a3c82e4d..423a4d84e276 100644
> > > --- a/src/libcamera/v4l2_base.cpp
> > > +++ b/src/libcamera/v4l2_base.cpp
> > > @@ -5,7 +5,13 @@
> > >   * v4l2_base.cpp - Common base for V4L2 devices and subdevices
> > >   */
> > >
> > > +#include <linux/v4l2-subdev.h>
> > > +#include <linux/videodev2.h>
> > > +#include <sys/ioctl.h>
> > > +
> > > +#include "log.h"
> > >  #include "v4l2_base.h"
>
> The issue comes from a previous patch, but v4l2_base.h should be the
> very first header included, to help ensuring it is self-contained.
>
> > > +#include "v4l2_controls.h"
> > >
> > >  /**
> > >   * \file v4l2_base.h
> > > @@ -14,6 +20,8 @@
> > >
> > >  namespace libcamera {
> > >
> > > +LOG_DEFINE_CATEGORY(V4L2Base)
> > > +
> > >  /**
> > >   * \class V4L2Base
> > >   * \brief Base class for V4L2Device and V4L2Subdevice
> > > @@ -31,6 +39,7 @@ namespace libcamera {
> > >   */
> > >
> > >  /**
> > > + * \fn V4L2Base::open()
>
> Does this belong to this patch ? Same for the next method.
>

No, sorry, bad rebasing

> > >   * \brief Open a V4L2 device or subdevice
> > >   *
> > >   * Pure virtual method to be implemented by the derived classes.
> > > @@ -39,6 +48,7 @@ namespace libcamera {
> > >   */
> > >
> > >  /**
> > > + * \fn V4L2Base::close()
> > >   * \brief Close the device or subdevice
> > >   *
> > >   * Pure virtual method to be implemented by the derived classes.
> > > @@ -53,6 +63,286 @@ bool V4L2Base::isOpen() const
> > >  	return fd_ != -1;
> > >  }
> > >
> > > +int V4L2Base::queryControl(unsigned int id,
> > > +			   struct v4l2_query_ext_ctrl *qext_ctrl)
> > > +{
> > > +	qext_ctrl->id = id;
> > > +
> > > +	int ret = ioctl(fd_, VIDIOC_QUERY_EXT_CTRL, qext_ctrl);
> > > +	if (ret) {
> > > +		ret = -errno;
> > > +		LOG(V4L2Base, Error)
> > > +			<< "Unable to query extended control 0x"
>
> s/extended //
>
> > > +			<< std::hex << qext_ctrl->id
> > > +			<< ": " << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	if (qext_ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
> > > +		LOG(V4L2Base, Error)
> > > +			<< "Extended control 0x" << std::hex
> > > +			<< qext_ctrl->id << " is disabled";
> > > +		return -EINVAL;
> > > +	}
>
> I would handle this in the caller, it's not a queryControl() error as
> such.
>
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \brief Get values of a list of control IDs
> > > + * \param ctrl_ids The list of control IDs to retrieve value of
> > > + * \return A list of V4L2Control on success or a empty list otherwise
> > > + */
> > > +std::vector<V4L2Control *> V4L2Base::getControls(std::vector<unsigned int> &ctrl_ids)
> > > +{
> > > +	unsigned int count = ctrl_ids.size();
> > > +	if (!count)
> > > +		return std::vector<V4L2Control *> {};
> > > +
> > > +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> > > +					      (new struct v4l2_ext_control[count]);
> > > +	if (!v4l2_ctrls)
> > > +		return std::vector<V4L2Control *> {};
> > > +
> > > +	/*
> > > +	 * Query each control in the vector to get back its type and expected
> > > +	 * size in order to get its current value.
> > > +	 */
> > > +	for (unsigned int i = 0; i < count; ++i) {
> > > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > > +		unsigned int ctrl_id = ctrl_ids[i];
> > > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > > +
> > > +		int ret = queryControl(ctrl_id, &qext_ctrl);
> > > +		if (ret)
> > > +			return std::vector<V4L2Control *> {};
> > > +
> > > +		v4l2_ctrl->id = ctrl_id;
> > > +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> > > +
> > > +		/* Reserve memory for controls with payload. */
> > > +		if (!(qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
> > > +			continue;
> > > +
> > > +		switch (qext_ctrl.type) {
> > > +		case V4L2_CTRL_TYPE_U8:
> > > +			v4l2_ctrl->p_u8 = static_cast<uint8_t *>
> > > +					  (new uint8_t[v4l2_ctrl->size]);
> > > +			break;
> > > +		case V4L2_CTRL_TYPE_U16:
> > > +			v4l2_ctrl->p_u16 = static_cast<uint16_t *>
> > > +					  (new uint16_t[v4l2_ctrl->size]);
> > > +			break;
> > > +		case V4L2_CTRL_TYPE_U32:
> > > +			v4l2_ctrl->p_u32 = static_cast<uint32_t *>
> > > +					  (new uint32_t[v4l2_ctrl->size]);
> > > +			break;
> > > +		}
> > > +	}
> > > +
> > > +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> > > +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> > > +	v4l2_ext_ctrls.count = count;
> > > +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> > > +
> > > +	int ret = ioctl(fd_, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls);
> > > +	if (ret) {
> > > +		ret = -errno;
> > > +		LOG(V4L2Base, Error)
> > > +			<< "Unable to get extended controls: " << strerror(-ret);
> > > +			return std::vector<V4L2Control *> {};
> > > +	}
> > > +
> > > +	/*
> > > +	 * For each requested control, create a V4L2Control which contains
> > > +	 * the current value and store it in the vector returned to the caller.
> > > +	 */
> > > +	std::vector<V4L2Control *> controls = {};
> > > +	for (unsigned int i = 0; i < count; ++i) {
> > > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > > +
> > > +		int ret = queryControl(v4l2_ctrl->id, &qext_ctrl);
> > > +		if (ret)
> > > +			return std::vector<V4L2Control *> {};
> > > +
> > > +		unsigned int size = qext_ctrl.elem_size * qext_ctrl.elem_size;
> > > +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> > > +			/* Control types with payload */
> > > +			switch (qext_ctrl.type) {
> > > +			case V4L2_CTRL_TYPE_U8:
> > > +			{
> > > +				V4L2U8Control *c =
> > > +					new V4L2U8Control(v4l2_ctrl->id, size,
> > > +							  v4l2_ctrl->p_u8);
> > > +				controls.push_back(c);
> > > +			}
> > > +				break;
> > > +			case V4L2_CTRL_TYPE_U16:
> > > +			{
> > > +				V4L2U16Control *c =
> > > +					new V4L2U16Control(v4l2_ctrl->id, size,
> > > +							   v4l2_ctrl->p_u16);
> > > +				controls.push_back(c);
> > > +			}
> > > +				break;
> > > +			case V4L2_CTRL_TYPE_U32:
> > > +			{
> > > +				V4L2U32Control *c =
> > > +					new V4L2U32Control(v4l2_ctrl->id, size,
> > > +							  v4l2_ctrl->p_u32);
> > > +				controls.push_back(c);
> > > +			}
> > > +				break;
> > > +			default:
> > > +				LOG(V4L2Base, Error)
> > > +					<< "Compound control types not supported: "
> > > +					<< std::hex << qext_ctrl.type;
> > > +				return std::vector<V4L2Control *> {};
> > > +			}
> > > +		} else {
> > > +			/* Control types without payload. */
> > > +			switch (qext_ctrl.type) {
> > > +			case V4L2_CTRL_TYPE_INTEGER64:
> > > +			{
> > > +				V4L2Int64Control *c =
> > > +					new V4L2Int64Control(v4l2_ctrl->id,
> > > +							     v4l2_ctrl->value64);
> > > +				controls.push_back(c);
> >
> > I think this could be a
> > 	controls.emplace(V4L2Int64Control(v4l2_ctrl->id,
> > 					  v4l2_ctrl->value64);
> >
> > if you prefer that style ...
> >
> > Aha - except you have a vector of pointers, rather than a vector of
> > objects ...
> >

Yes, I can't do that.

> > > +			}
> > > +				break;
> > > +			case V4L2_CTRL_TYPE_INTEGER:
> > > +			case V4L2_CTRL_TYPE_BOOLEAN:
> > > +			case V4L2_CTRL_TYPE_MENU:
> > > +			case V4L2_CTRL_TYPE_BUTTON:
> > > +			{
> > > +				V4L2IntControl *c =
> > > +					new V4L2IntControl(v4l2_ctrl->id,
> > > +							   v4l2_ctrl->value);
> > > +				controls.push_back(c);
> > > +			}
> > > +				break;
> > > +			default:
> > > +				LOG(V4L2Base, Error)
> > > +					<< "Invalid non-compound control type :"
> > > +					<< std::hex << qext_ctrl.type;
> > > +				return std::vector<V4L2Control *> {};
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	return controls;
> > > +}
> > > +
> > > +/**
> > > + * \brief Get the value of control with id \a ctrl_id
> > > + * \param ctrl_id The control id
> > > + * \return The V4L2Control which represent the value of the control on success
> > > + * nullptr otherwise
> > > + */
> > > +V4L2Control *V4L2Base::getControl(unsigned int ctrl_id)
> > > +{
> > > +	std::vector<unsigned int> v = { ctrl_id, };
> > > +	std::vector<V4L2Control *> ctrls = getControls(v);
> > > +
> > > +	if (ctrls.empty())
> > > +		return nullptr;
> > > +
> > > +	return ctrls[0];
> > > +}
> > > +
> > > +/**
> > > + * \brief Apply a list of controls to the device
> > > + * \param ctrls The list of controls to apply
> > > + * \return 0 on success or a negative error code otherwise
> > > + */
> > > +int V4L2Base::setControls(std::vector<V4L2Control *> &ctrls)
> > > +{
> > > +	unsigned int count = ctrls.size();
>
> size_t instead of unsigned int ?
>
> > > +	if (!count)
> > > +		return -EINVAL;
> > > +
> > > +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> > > +					      (new struct v4l2_ext_control[count]);
>
> Do you need the cast ?
>
> > > +	if (!v4l2_ctrls)
> > > +		return -ENOMEM;
>
> new() will throw an exception if it can't construct the object, so
> there's no need to check the return value.
>
> > > +
> > > +	/*
> > > +	 * Query each control in the vector to get back its type and expected
> > > +	 * size and set the new desired value in each v4l2_ext_control member.
> > > +	 */
> > > +	for (unsigned int i = 0; i < count; ++i) {
> > > +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> > > +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> > > +		V4L2Control *ctrl = ctrls[i];
> > > +
> > > +		int ret = queryControl(ctrl->id(), &qext_ctrl);
> > > +		if (ret)
> > > +			return ret;
>
> It's annoying to have to query each control every time. We should either
> enumerate all of them when opening the device, or query them at runtime
> but cache the control information.

I'm not a fan of caching control info at open() time tbh. It's quite a
relevant amount of memory to be kept around. Indeed I call
queryControl twice for each control, and this is bad. I could cache
the information -inside this method- and reduce the number of calls to
1, which seems acceptable to me.

>
> > > +
> > > +		v4l2_ctrl->id = ctrl->id();
> > > +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> > > +
> > > +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> > > +			/** \todo: Support set controls with payload. */
> > > +			LOG(V4L2Base, Error)
> > > +				<< "Controls with payload not supported";
> > > +			return -EINVAL;
> > > +		} else {
> > > +			switch (qext_ctrl.type) {
> > > +			case V4L2_CTRL_TYPE_INTEGER64:
> > > +			{
> > > +				V4L2Int64Control *c =
> > > +					static_cast<V4L2Int64Control *>(ctrl);
> >
> > It's these static_casts that feel a bit horrible to me. In my
> > implementation there is just single class, so there is no casting necessary,
>
> I don't like the casts much either, especially given that you cast based
> on the type returned by the kernel, which could possibly not match the
> type of the control as set by the application. This would likely lead to
> memory corruption and crashes.

I see. Wrapping the container as you suggested and let users interface
with that would probably mask the casting inside the V4L2Control
class, and it would alway happen on the control type as returned by
the kernel. That should be better...

>
> > > +				v4l2_ctrl->value64 = c->value();
> >
> > However, this would be c->getInteger64();
> >
> > > +			}
> > > +				break;
> > > +			case V4L2_CTRL_TYPE_INTEGER:
> > > +			case V4L2_CTRL_TYPE_BOOLEAN:
> > > +			case V4L2_CTRL_TYPE_MENU:
> > > +			case V4L2_CTRL_TYPE_BUTTON:
> > > +			{
> > > +				V4L2IntControl *c =
> > > +					static_cast<V4L2IntControl *>(ctrl);
> > > +				v4l2_ctrl->value = c->value();
> >
> > and getInteger(); (although getBoolean() can be separated as well);
> >
> > As we know the types in this switch, I wonder if there is much
> > difference int taste between the two approaches...
> >

I think I should try to mask the casting in the V4L2Control class as
much as I could. I'm still not sure this is completely possible
though.

Thanks
   j

> > > +			}
> > > +				break;
> > > +			default:
> > > +				LOG(V4L2Base, Error)
> > > +					<< "Invalid non-compound control type :"
> > > +					<< qext_ctrl.type;
> > > +				return -EINVAL;
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> > > +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> > > +	v4l2_ext_ctrls.count = count;
> > > +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> > > +
> > > +	int ret = ioctl(fd_, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls);
> > > +	if (ret) {
> > > +		ret = -errno;
> > > +		LOG(V4L2Base, Error)
> > > +			<< "Unable to set extended controls: " << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/**
> > > + * \brief Apply the control \a ctrl to the device
> > > + * \param ctrl The control to apply to the device
> > > + * \return 0 on success, a negative error code otherwise
> > > + */
> > > +int V4L2Base::setControl(V4L2Control *ctrl)
> > > +{
> > > +	std::vector<V4L2Control *> v = { ctrl, };
> > > +	return setControls(v);
> > > +}
> > > +
> > >  /**
> > >   * \var V4L2Base::fd_
> > >   * \brief The V4L2 device or subdevice device node file descriptor
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart June 10, 2019, 8:35 a.m. UTC | #4
Hi Jacopo,

On Mon, Jun 10, 2019 at 09:38:42AM +0200, Jacopo Mondi wrote:
> On Sat, Jun 08, 2019 at 07:07:08PM +0300, Laurent Pinchart wrote:
> > On Mon, Jun 03, 2019 at 01:32:58PM +0100, Kieran Bingham wrote:
> >> On 02/06/2019 14:04, Jacopo Mondi wrote:
> >>> Implement operations to set and get V4L2 controls in the V4L2Base class.
> >>> The operations allow to set and get a single or a list of controls.
> >>>
> >>> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> >>> ---
> >>>  src/libcamera/include/v4l2_base.h |  12 ++
> >>>  src/libcamera/v4l2_base.cpp       | 290 ++++++++++++++++++++++++++++++
> >>>  2 files changed, 302 insertions(+)
> >>>
> >>> diff --git a/src/libcamera/include/v4l2_base.h b/src/libcamera/include/v4l2_base.h
> >>> index 2fda81a960d2..7d4e238dadfa 100644
> >>> --- a/src/libcamera/include/v4l2_base.h
> >>> +++ b/src/libcamera/include/v4l2_base.h
> >>> @@ -9,6 +9,8 @@
> >>>
> >>>  #include <vector>
> >>>
> >>> +#include <v4l2_controls.h>
> >
> > This should be "v4l2_controls.h", but you can simply forward-declare the
> > V4L2Control class instead.
> >
> >>> +
> >>>  namespace libcamera {
> >>>
> >>>  class V4L2Base
> >>> @@ -22,8 +24,18 @@ public:
> >>>  	virtual void close() = 0;
> >>>  	bool isOpen() const;
> >>>
> >>> +	std::vector<V4L2Control *> getControls(std::vector<unsigned int> &ctrl_ids);
> >
> > A vector of pointer isn't very nice as it requires the caller to delete
> > each item. I would prefer passing a reference to a vector (or map) of
> > values to the function, and having it fill the values.
> 
> I thought about it, but how would you define the state of a Control ?
> Would you create a control with id but without any associated value ?
> And what if you call value() on it? I fear keeping track of that is a
> pain, and I preferred to have controls that once created always have a
> value associated.
> 
> I can reconsider it though.

Having to delete them explicitly is a real no-go in my opinion. And even
if we could solve that, allocating them separately isn't nice from a
performance point of view. I'm sure multiple APIs are possible, so feel
free to propose an alternative :-)

> >> In my controls, I pass a list <well a map, or a set; I have two
> >> implementations currently> of the actual ControlValue objects, which get
> >> filled in.
> >>
> >> I wonder if there will be much differences between the two interface
> >> designs.
> >
> > One big difference is that a vector is ordered while a map or set isn't.
> > The order in which controls are set can make a difference.
> 
> I think Kieran referred to the "container of controls to fill" vs the
> "get control ids and return a container of controls" different
> approaches.
> 
> >>> +	V4L2Control *getControl(unsigned int ctrl_id);
> >>> +	int setControls(std::vector<V4L2Control *> &ctrls);
> >>> +	int setControl(V4L2Control *ctrl);
> >>
> >> I think I'd group those as getControls setControls, and then getControl,
> >> setControl, so that they are grouped by 'functionality' but that might
> >> not be a preferred sorting... so either way.
> >
> > I would prefer that as well.
> 
> Yup.
> 
> >>> +
> >>>  protected:
> >>>  	int fd_;
> >>> +
> >>> +private:
> >>> +	int queryControl(unsigned int ctrl_id,
> >>> +			 struct v4l2_query_ext_ctrl *qext_ctrl);
> >>> +
> >>>  };
> >>>
> >>>  }; /* namespace libcamera */
> >>> diff --git a/src/libcamera/v4l2_base.cpp b/src/libcamera/v4l2_base.cpp
> >>> index 7d05a3c82e4d..423a4d84e276 100644
> >>> --- a/src/libcamera/v4l2_base.cpp
> >>> +++ b/src/libcamera/v4l2_base.cpp
> >>> @@ -5,7 +5,13 @@
> >>>   * v4l2_base.cpp - Common base for V4L2 devices and subdevices
> >>>   */
> >>>
> >>> +#include <linux/v4l2-subdev.h>
> >>> +#include <linux/videodev2.h>
> >>> +#include <sys/ioctl.h>
> >>> +
> >>> +#include "log.h"
> >>>  #include "v4l2_base.h"
> >
> > The issue comes from a previous patch, but v4l2_base.h should be the
> > very first header included, to help ensuring it is self-contained.
> >
> >>> +#include "v4l2_controls.h"
> >>>
> >>>  /**
> >>>   * \file v4l2_base.h
> >>> @@ -14,6 +20,8 @@
> >>>
> >>>  namespace libcamera {
> >>>
> >>> +LOG_DEFINE_CATEGORY(V4L2Base)
> >>> +
> >>>  /**
> >>>   * \class V4L2Base
> >>>   * \brief Base class for V4L2Device and V4L2Subdevice
> >>> @@ -31,6 +39,7 @@ namespace libcamera {
> >>>   */
> >>>
> >>>  /**
> >>> + * \fn V4L2Base::open()
> >
> > Does this belong to this patch ? Same for the next method.
> 
> No, sorry, bad rebasing
> 
> >>>   * \brief Open a V4L2 device or subdevice
> >>>   *
> >>>   * Pure virtual method to be implemented by the derived classes.
> >>> @@ -39,6 +48,7 @@ namespace libcamera {
> >>>   */
> >>>
> >>>  /**
> >>> + * \fn V4L2Base::close()
> >>>   * \brief Close the device or subdevice
> >>>   *
> >>>   * Pure virtual method to be implemented by the derived classes.
> >>> @@ -53,6 +63,286 @@ bool V4L2Base::isOpen() const
> >>>  	return fd_ != -1;
> >>>  }
> >>>
> >>> +int V4L2Base::queryControl(unsigned int id,
> >>> +			   struct v4l2_query_ext_ctrl *qext_ctrl)
> >>> +{
> >>> +	qext_ctrl->id = id;
> >>> +
> >>> +	int ret = ioctl(fd_, VIDIOC_QUERY_EXT_CTRL, qext_ctrl);
> >>> +	if (ret) {
> >>> +		ret = -errno;
> >>> +		LOG(V4L2Base, Error)
> >>> +			<< "Unable to query extended control 0x"
> >
> > s/extended //
> >
> >>> +			<< std::hex << qext_ctrl->id
> >>> +			<< ": " << strerror(-ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	if (qext_ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
> >>> +		LOG(V4L2Base, Error)
> >>> +			<< "Extended control 0x" << std::hex
> >>> +			<< qext_ctrl->id << " is disabled";
> >>> +		return -EINVAL;
> >>> +	}
> >
> > I would handle this in the caller, it's not a queryControl() error as
> > such.
> >
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * \brief Get values of a list of control IDs
> >>> + * \param ctrl_ids The list of control IDs to retrieve value of
> >>> + * \return A list of V4L2Control on success or a empty list otherwise
> >>> + */
> >>> +std::vector<V4L2Control *> V4L2Base::getControls(std::vector<unsigned int> &ctrl_ids)
> >>> +{
> >>> +	unsigned int count = ctrl_ids.size();
> >>> +	if (!count)
> >>> +		return std::vector<V4L2Control *> {};
> >>> +
> >>> +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> >>> +					      (new struct v4l2_ext_control[count]);
> >>> +	if (!v4l2_ctrls)
> >>> +		return std::vector<V4L2Control *> {};
> >>> +
> >>> +	/*
> >>> +	 * Query each control in the vector to get back its type and expected
> >>> +	 * size in order to get its current value.
> >>> +	 */
> >>> +	for (unsigned int i = 0; i < count; ++i) {
> >>> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> >>> +		unsigned int ctrl_id = ctrl_ids[i];
> >>> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> >>> +
> >>> +		int ret = queryControl(ctrl_id, &qext_ctrl);
> >>> +		if (ret)
> >>> +			return std::vector<V4L2Control *> {};
> >>> +
> >>> +		v4l2_ctrl->id = ctrl_id;
> >>> +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> >>> +
> >>> +		/* Reserve memory for controls with payload. */
> >>> +		if (!(qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
> >>> +			continue;
> >>> +
> >>> +		switch (qext_ctrl.type) {
> >>> +		case V4L2_CTRL_TYPE_U8:
> >>> +			v4l2_ctrl->p_u8 = static_cast<uint8_t *>
> >>> +					  (new uint8_t[v4l2_ctrl->size]);
> >>> +			break;
> >>> +		case V4L2_CTRL_TYPE_U16:
> >>> +			v4l2_ctrl->p_u16 = static_cast<uint16_t *>
> >>> +					  (new uint16_t[v4l2_ctrl->size]);
> >>> +			break;
> >>> +		case V4L2_CTRL_TYPE_U32:
> >>> +			v4l2_ctrl->p_u32 = static_cast<uint32_t *>
> >>> +					  (new uint32_t[v4l2_ctrl->size]);
> >>> +			break;
> >>> +		}
> >>> +	}
> >>> +
> >>> +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> >>> +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> >>> +	v4l2_ext_ctrls.count = count;
> >>> +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> >>> +
> >>> +	int ret = ioctl(fd_, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls);
> >>> +	if (ret) {
> >>> +		ret = -errno;
> >>> +		LOG(V4L2Base, Error)
> >>> +			<< "Unable to get extended controls: " << strerror(-ret);
> >>> +			return std::vector<V4L2Control *> {};
> >>> +	}
> >>> +
> >>> +	/*
> >>> +	 * For each requested control, create a V4L2Control which contains
> >>> +	 * the current value and store it in the vector returned to the caller.
> >>> +	 */
> >>> +	std::vector<V4L2Control *> controls = {};
> >>> +	for (unsigned int i = 0; i < count; ++i) {
> >>> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> >>> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> >>> +
> >>> +		int ret = queryControl(v4l2_ctrl->id, &qext_ctrl);
> >>> +		if (ret)
> >>> +			return std::vector<V4L2Control *> {};
> >>> +
> >>> +		unsigned int size = qext_ctrl.elem_size * qext_ctrl.elem_size;
> >>> +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> >>> +			/* Control types with payload */
> >>> +			switch (qext_ctrl.type) {
> >>> +			case V4L2_CTRL_TYPE_U8:
> >>> +			{
> >>> +				V4L2U8Control *c =
> >>> +					new V4L2U8Control(v4l2_ctrl->id, size,
> >>> +							  v4l2_ctrl->p_u8);
> >>> +				controls.push_back(c);
> >>> +			}
> >>> +				break;
> >>> +			case V4L2_CTRL_TYPE_U16:
> >>> +			{
> >>> +				V4L2U16Control *c =
> >>> +					new V4L2U16Control(v4l2_ctrl->id, size,
> >>> +							   v4l2_ctrl->p_u16);
> >>> +				controls.push_back(c);
> >>> +			}
> >>> +				break;
> >>> +			case V4L2_CTRL_TYPE_U32:
> >>> +			{
> >>> +				V4L2U32Control *c =
> >>> +					new V4L2U32Control(v4l2_ctrl->id, size,
> >>> +							  v4l2_ctrl->p_u32);
> >>> +				controls.push_back(c);
> >>> +			}
> >>> +				break;
> >>> +			default:
> >>> +				LOG(V4L2Base, Error)
> >>> +					<< "Compound control types not supported: "
> >>> +					<< std::hex << qext_ctrl.type;
> >>> +				return std::vector<V4L2Control *> {};
> >>> +			}
> >>> +		} else {
> >>> +			/* Control types without payload. */
> >>> +			switch (qext_ctrl.type) {
> >>> +			case V4L2_CTRL_TYPE_INTEGER64:
> >>> +			{
> >>> +				V4L2Int64Control *c =
> >>> +					new V4L2Int64Control(v4l2_ctrl->id,
> >>> +							     v4l2_ctrl->value64);
> >>> +				controls.push_back(c);
> >>
> >> I think this could be a
> >> 	controls.emplace(V4L2Int64Control(v4l2_ctrl->id,
> >> 					  v4l2_ctrl->value64);
> >>
> >> if you prefer that style ...
> >>
> >> Aha - except you have a vector of pointers, rather than a vector of
> >> objects ...
> 
> Yes, I can't do that.
> 
> >>> +			}
> >>> +				break;
> >>> +			case V4L2_CTRL_TYPE_INTEGER:
> >>> +			case V4L2_CTRL_TYPE_BOOLEAN:
> >>> +			case V4L2_CTRL_TYPE_MENU:
> >>> +			case V4L2_CTRL_TYPE_BUTTON:
> >>> +			{
> >>> +				V4L2IntControl *c =
> >>> +					new V4L2IntControl(v4l2_ctrl->id,
> >>> +							   v4l2_ctrl->value);
> >>> +				controls.push_back(c);
> >>> +			}
> >>> +				break;
> >>> +			default:
> >>> +				LOG(V4L2Base, Error)
> >>> +					<< "Invalid non-compound control type :"
> >>> +					<< std::hex << qext_ctrl.type;
> >>> +				return std::vector<V4L2Control *> {};
> >>> +			}
> >>> +		}
> >>> +	}
> >>> +
> >>> +	return controls;
> >>> +}
> >>> +
> >>> +/**
> >>> + * \brief Get the value of control with id \a ctrl_id
> >>> + * \param ctrl_id The control id
> >>> + * \return The V4L2Control which represent the value of the control on success
> >>> + * nullptr otherwise
> >>> + */
> >>> +V4L2Control *V4L2Base::getControl(unsigned int ctrl_id)
> >>> +{
> >>> +	std::vector<unsigned int> v = { ctrl_id, };
> >>> +	std::vector<V4L2Control *> ctrls = getControls(v);
> >>> +
> >>> +	if (ctrls.empty())
> >>> +		return nullptr;
> >>> +
> >>> +	return ctrls[0];
> >>> +}
> >>> +
> >>> +/**
> >>> + * \brief Apply a list of controls to the device
> >>> + * \param ctrls The list of controls to apply
> >>> + * \return 0 on success or a negative error code otherwise
> >>> + */
> >>> +int V4L2Base::setControls(std::vector<V4L2Control *> &ctrls)
> >>> +{
> >>> +	unsigned int count = ctrls.size();
> >
> > size_t instead of unsigned int ?
> >
> >>> +	if (!count)
> >>> +		return -EINVAL;
> >>> +
> >>> +	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
> >>> +					      (new struct v4l2_ext_control[count]);
> >
> > Do you need the cast ?
> >
> >>> +	if (!v4l2_ctrls)
> >>> +		return -ENOMEM;
> >
> > new() will throw an exception if it can't construct the object, so
> > there's no need to check the return value.
> >
> >>> +
> >>> +	/*
> >>> +	 * Query each control in the vector to get back its type and expected
> >>> +	 * size and set the new desired value in each v4l2_ext_control member.
> >>> +	 */
> >>> +	for (unsigned int i = 0; i < count; ++i) {
> >>> +		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
> >>> +		struct v4l2_query_ext_ctrl qext_ctrl = {};
> >>> +		V4L2Control *ctrl = ctrls[i];
> >>> +
> >>> +		int ret = queryControl(ctrl->id(), &qext_ctrl);
> >>> +		if (ret)
> >>> +			return ret;
> >
> > It's annoying to have to query each control every time. We should either
> > enumerate all of them when opening the device, or query them at runtime
> > but cache the control information.
> 
> I'm not a fan of caching control info at open() time tbh. It's quite a
> relevant amount of memory to be kept around. Indeed I call
> queryControl twice for each control, and this is bad. I could cache
> the information -inside this method- and reduce the number of calls to
> 1, which seems acceptable to me.

This will potentially be called for every request. The memory cost of a
cache that would avoid calling queryControl() once per request is a
small price to pay. We don't necessarily have to do it at open() time,
it could be cached the first time the control is queried (although that
would save time at open() time but introduce less-deterministic delays
at run time, which may not be a good idea).

> >>> +
> >>> +		v4l2_ctrl->id = ctrl->id();
> >>> +		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
> >>> +
> >>> +		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
> >>> +			/** \todo: Support set controls with payload. */
> >>> +			LOG(V4L2Base, Error)
> >>> +				<< "Controls with payload not supported";
> >>> +			return -EINVAL;
> >>> +		} else {
> >>> +			switch (qext_ctrl.type) {
> >>> +			case V4L2_CTRL_TYPE_INTEGER64:
> >>> +			{
> >>> +				V4L2Int64Control *c =
> >>> +					static_cast<V4L2Int64Control *>(ctrl);
> >>
> >> It's these static_casts that feel a bit horrible to me. In my
> >> implementation there is just single class, so there is no casting necessary,
> >
> > I don't like the casts much either, especially given that you cast based
> > on the type returned by the kernel, which could possibly not match the
> > type of the control as set by the application. This would likely lead to
> > memory corruption and crashes.
> 
> I see. Wrapping the container as you suggested and let users interface
> with that would probably mask the casting inside the V4L2Control
> class, and it would alway happen on the control type as returned by
> the kernel. That should be better...
> 
> >>> +				v4l2_ctrl->value64 = c->value();
> >>
> >> However, this would be c->getInteger64();
> >>
> >>> +			}
> >>> +				break;
> >>> +			case V4L2_CTRL_TYPE_INTEGER:
> >>> +			case V4L2_CTRL_TYPE_BOOLEAN:
> >>> +			case V4L2_CTRL_TYPE_MENU:
> >>> +			case V4L2_CTRL_TYPE_BUTTON:
> >>> +			{
> >>> +				V4L2IntControl *c =
> >>> +					static_cast<V4L2IntControl *>(ctrl);
> >>> +				v4l2_ctrl->value = c->value();
> >>
> >> and getInteger(); (although getBoolean() can be separated as well);
> >>
> >> As we know the types in this switch, I wonder if there is much
> >> difference int taste between the two approaches...
> 
> I think I should try to mask the casting in the V4L2Control class as
> much as I could. I'm still not sure this is completely possible
> though.

Maybe not completely possible, but if we can avoid it in most cases it
would already be a good improvement.

> >>> +			}
> >>> +				break;
> >>> +			default:
> >>> +				LOG(V4L2Base, Error)
> >>> +					<< "Invalid non-compound control type :"
> >>> +					<< qext_ctrl.type;
> >>> +				return -EINVAL;
> >>> +			}
> >>> +		}
> >>> +	}
> >>> +
> >>> +	struct v4l2_ext_controls v4l2_ext_ctrls = {};
> >>> +	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
> >>> +	v4l2_ext_ctrls.count = count;
> >>> +	v4l2_ext_ctrls.controls = v4l2_ctrls;
> >>> +
> >>> +	int ret = ioctl(fd_, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls);
> >>> +	if (ret) {
> >>> +		ret = -errno;
> >>> +		LOG(V4L2Base, Error)
> >>> +			<< "Unable to set extended controls: " << strerror(-ret);
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * \brief Apply the control \a ctrl to the device
> >>> + * \param ctrl The control to apply to the device
> >>> + * \return 0 on success, a negative error code otherwise
> >>> + */
> >>> +int V4L2Base::setControl(V4L2Control *ctrl)
> >>> +{
> >>> +	std::vector<V4L2Control *> v = { ctrl, };
> >>> +	return setControls(v);
> >>> +}
> >>> +
> >>>  /**
> >>>   * \var V4L2Base::fd_
> >>>   * \brief The V4L2 device or subdevice device node file descriptor

Patch

diff --git a/src/libcamera/include/v4l2_base.h b/src/libcamera/include/v4l2_base.h
index 2fda81a960d2..7d4e238dadfa 100644
--- a/src/libcamera/include/v4l2_base.h
+++ b/src/libcamera/include/v4l2_base.h
@@ -9,6 +9,8 @@ 
 
 #include <vector>
 
+#include <v4l2_controls.h>
+
 namespace libcamera {
 
 class V4L2Base
@@ -22,8 +24,18 @@  public:
 	virtual void close() = 0;
 	bool isOpen() const;
 
+	std::vector<V4L2Control *> getControls(std::vector<unsigned int> &ctrl_ids);
+	V4L2Control *getControl(unsigned int ctrl_id);
+	int setControls(std::vector<V4L2Control *> &ctrls);
+	int setControl(V4L2Control *ctrl);
+
 protected:
 	int fd_;
+
+private:
+	int queryControl(unsigned int ctrl_id,
+			 struct v4l2_query_ext_ctrl *qext_ctrl);
+
 };
 
 }; /* namespace libcamera */
diff --git a/src/libcamera/v4l2_base.cpp b/src/libcamera/v4l2_base.cpp
index 7d05a3c82e4d..423a4d84e276 100644
--- a/src/libcamera/v4l2_base.cpp
+++ b/src/libcamera/v4l2_base.cpp
@@ -5,7 +5,13 @@ 
  * v4l2_base.cpp - Common base for V4L2 devices and subdevices
  */
 
+#include <linux/v4l2-subdev.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+
+#include "log.h"
 #include "v4l2_base.h"
+#include "v4l2_controls.h"
 
 /**
  * \file v4l2_base.h
@@ -14,6 +20,8 @@ 
 
 namespace libcamera {
 
+LOG_DEFINE_CATEGORY(V4L2Base)
+
 /**
  * \class V4L2Base
  * \brief Base class for V4L2Device and V4L2Subdevice
@@ -31,6 +39,7 @@  namespace libcamera {
  */
 
 /**
+ * \fn V4L2Base::open()
  * \brief Open a V4L2 device or subdevice
  *
  * Pure virtual method to be implemented by the derived classes.
@@ -39,6 +48,7 @@  namespace libcamera {
  */
 
 /**
+ * \fn V4L2Base::close()
  * \brief Close the device or subdevice
  *
  * Pure virtual method to be implemented by the derived classes.
@@ -53,6 +63,286 @@  bool V4L2Base::isOpen() const
 	return fd_ != -1;
 }
 
+int V4L2Base::queryControl(unsigned int id,
+			   struct v4l2_query_ext_ctrl *qext_ctrl)
+{
+	qext_ctrl->id = id;
+
+	int ret = ioctl(fd_, VIDIOC_QUERY_EXT_CTRL, qext_ctrl);
+	if (ret) {
+		ret = -errno;
+		LOG(V4L2Base, Error)
+			<< "Unable to query extended control 0x"
+			<< std::hex << qext_ctrl->id
+			<< ": " << strerror(-ret);
+		return ret;
+	}
+
+	if (qext_ctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
+		LOG(V4L2Base, Error)
+			<< "Extended control 0x" << std::hex
+			<< qext_ctrl->id << " is disabled";
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Get values of a list of control IDs
+ * \param ctrl_ids The list of control IDs to retrieve value of
+ * \return A list of V4L2Control on success or a empty list otherwise
+ */
+std::vector<V4L2Control *> V4L2Base::getControls(std::vector<unsigned int> &ctrl_ids)
+{
+	unsigned int count = ctrl_ids.size();
+	if (!count)
+		return std::vector<V4L2Control *> {};
+
+	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
+					      (new struct v4l2_ext_control[count]);
+	if (!v4l2_ctrls)
+		return std::vector<V4L2Control *> {};
+
+	/*
+	 * Query each control in the vector to get back its type and expected
+	 * size in order to get its current value.
+	 */
+	for (unsigned int i = 0; i < count; ++i) {
+		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
+		unsigned int ctrl_id = ctrl_ids[i];
+		struct v4l2_query_ext_ctrl qext_ctrl = {};
+
+		int ret = queryControl(ctrl_id, &qext_ctrl);
+		if (ret)
+			return std::vector<V4L2Control *> {};
+
+		v4l2_ctrl->id = ctrl_id;
+		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
+
+		/* Reserve memory for controls with payload. */
+		if (!(qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD))
+			continue;
+
+		switch (qext_ctrl.type) {
+		case V4L2_CTRL_TYPE_U8:
+			v4l2_ctrl->p_u8 = static_cast<uint8_t *>
+					  (new uint8_t[v4l2_ctrl->size]);
+			break;
+		case V4L2_CTRL_TYPE_U16:
+			v4l2_ctrl->p_u16 = static_cast<uint16_t *>
+					  (new uint16_t[v4l2_ctrl->size]);
+			break;
+		case V4L2_CTRL_TYPE_U32:
+			v4l2_ctrl->p_u32 = static_cast<uint32_t *>
+					  (new uint32_t[v4l2_ctrl->size]);
+			break;
+		}
+	}
+
+	struct v4l2_ext_controls v4l2_ext_ctrls = {};
+	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
+	v4l2_ext_ctrls.count = count;
+	v4l2_ext_ctrls.controls = v4l2_ctrls;
+
+	int ret = ioctl(fd_, VIDIOC_G_EXT_CTRLS, &v4l2_ext_ctrls);
+	if (ret) {
+		ret = -errno;
+		LOG(V4L2Base, Error)
+			<< "Unable to get extended controls: " << strerror(-ret);
+			return std::vector<V4L2Control *> {};
+	}
+
+	/*
+	 * For each requested control, create a V4L2Control which contains
+	 * the current value and store it in the vector returned to the caller.
+	 */
+	std::vector<V4L2Control *> controls = {};
+	for (unsigned int i = 0; i < count; ++i) {
+		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
+		struct v4l2_query_ext_ctrl qext_ctrl = {};
+
+		int ret = queryControl(v4l2_ctrl->id, &qext_ctrl);
+		if (ret)
+			return std::vector<V4L2Control *> {};
+
+		unsigned int size = qext_ctrl.elem_size * qext_ctrl.elem_size;
+		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
+			/* Control types with payload */
+			switch (qext_ctrl.type) {
+			case V4L2_CTRL_TYPE_U8:
+			{
+				V4L2U8Control *c =
+					new V4L2U8Control(v4l2_ctrl->id, size,
+							  v4l2_ctrl->p_u8);
+				controls.push_back(c);
+			}
+				break;
+			case V4L2_CTRL_TYPE_U16:
+			{
+				V4L2U16Control *c =
+					new V4L2U16Control(v4l2_ctrl->id, size,
+							   v4l2_ctrl->p_u16);
+				controls.push_back(c);
+			}
+				break;
+			case V4L2_CTRL_TYPE_U32:
+			{
+				V4L2U32Control *c =
+					new V4L2U32Control(v4l2_ctrl->id, size,
+							  v4l2_ctrl->p_u32);
+				controls.push_back(c);
+			}
+				break;
+			default:
+				LOG(V4L2Base, Error)
+					<< "Compound control types not supported: "
+					<< std::hex << qext_ctrl.type;
+				return std::vector<V4L2Control *> {};
+			}
+		} else {
+			/* Control types without payload. */
+			switch (qext_ctrl.type) {
+			case V4L2_CTRL_TYPE_INTEGER64:
+			{
+				V4L2Int64Control *c =
+					new V4L2Int64Control(v4l2_ctrl->id,
+							     v4l2_ctrl->value64);
+				controls.push_back(c);
+			}
+				break;
+			case V4L2_CTRL_TYPE_INTEGER:
+			case V4L2_CTRL_TYPE_BOOLEAN:
+			case V4L2_CTRL_TYPE_MENU:
+			case V4L2_CTRL_TYPE_BUTTON:
+			{
+				V4L2IntControl *c =
+					new V4L2IntControl(v4l2_ctrl->id,
+							   v4l2_ctrl->value);
+				controls.push_back(c);
+			}
+				break;
+			default:
+				LOG(V4L2Base, Error)
+					<< "Invalid non-compound control type :"
+					<< std::hex << qext_ctrl.type;
+				return std::vector<V4L2Control *> {};
+			}
+		}
+	}
+
+	return controls;
+}
+
+/**
+ * \brief Get the value of control with id \a ctrl_id
+ * \param ctrl_id The control id
+ * \return The V4L2Control which represent the value of the control on success
+ * nullptr otherwise
+ */
+V4L2Control *V4L2Base::getControl(unsigned int ctrl_id)
+{
+	std::vector<unsigned int> v = { ctrl_id, };
+	std::vector<V4L2Control *> ctrls = getControls(v);
+
+	if (ctrls.empty())
+		return nullptr;
+
+	return ctrls[0];
+}
+
+/**
+ * \brief Apply a list of controls to the device
+ * \param ctrls The list of controls to apply
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Base::setControls(std::vector<V4L2Control *> &ctrls)
+{
+	unsigned int count = ctrls.size();
+	if (!count)
+		return -EINVAL;
+
+	struct v4l2_ext_control *v4l2_ctrls = static_cast<struct v4l2_ext_control *>
+					      (new struct v4l2_ext_control[count]);
+	if (!v4l2_ctrls)
+		return -ENOMEM;
+
+	/*
+	 * Query each control in the vector to get back its type and expected
+	 * size and set the new desired value in each v4l2_ext_control member.
+	 */
+	for (unsigned int i = 0; i < count; ++i) {
+		struct v4l2_ext_control *v4l2_ctrl = &v4l2_ctrls[i];
+		struct v4l2_query_ext_ctrl qext_ctrl = {};
+		V4L2Control *ctrl = ctrls[i];
+
+		int ret = queryControl(ctrl->id(), &qext_ctrl);
+		if (ret)
+			return ret;
+
+		v4l2_ctrl->id = ctrl->id();
+		v4l2_ctrl->size = qext_ctrl.elem_size * qext_ctrl.elems;
+
+		if (qext_ctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
+			/** \todo: Support set controls with payload. */
+			LOG(V4L2Base, Error)
+				<< "Controls with payload not supported";
+			return -EINVAL;
+		} else {
+			switch (qext_ctrl.type) {
+			case V4L2_CTRL_TYPE_INTEGER64:
+			{
+				V4L2Int64Control *c =
+					static_cast<V4L2Int64Control *>(ctrl);
+				v4l2_ctrl->value64 = c->value();
+			}
+				break;
+			case V4L2_CTRL_TYPE_INTEGER:
+			case V4L2_CTRL_TYPE_BOOLEAN:
+			case V4L2_CTRL_TYPE_MENU:
+			case V4L2_CTRL_TYPE_BUTTON:
+			{
+				V4L2IntControl *c =
+					static_cast<V4L2IntControl *>(ctrl);
+				v4l2_ctrl->value = c->value();
+			}
+				break;
+			default:
+				LOG(V4L2Base, Error)
+					<< "Invalid non-compound control type :"
+					<< qext_ctrl.type;
+				return -EINVAL;
+			}
+		}
+	}
+
+	struct v4l2_ext_controls v4l2_ext_ctrls = {};
+	v4l2_ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
+	v4l2_ext_ctrls.count = count;
+	v4l2_ext_ctrls.controls = v4l2_ctrls;
+
+	int ret = ioctl(fd_, VIDIOC_S_EXT_CTRLS, &v4l2_ext_ctrls);
+	if (ret) {
+		ret = -errno;
+		LOG(V4L2Base, Error)
+			<< "Unable to set extended controls: " << strerror(-ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Apply the control \a ctrl to the device
+ * \param ctrl The control to apply to the device
+ * \return 0 on success, a negative error code otherwise
+ */
+int V4L2Base::setControl(V4L2Control *ctrl)
+{
+	std::vector<V4L2Control *> v = { ctrl, };
+	return setControls(v);
+}
+
 /**
  * \var V4L2Base::fd_
  * \brief The V4L2 device or subdevice device node file descriptor