[libcamera-devel,03/10] libcamera: ipu3: Initialize and link ImgU devices

Message ID 20190228200410.3022-4-jacopo@jmondi.org
State Superseded
Headers show
Series
  • libcamera: ipu3: ImgU support
Related show

Commit Message

Jacopo Mondi Feb. 28, 2019, 8:04 p.m. UTC
Create video devices and subdevices associated with an ImgU unit, and
link the entities in the media graph to prepare the device for capture
operations at stream configuration time.

As we support a single stream at the moment, always select imgu0.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
 1 file changed, 207 insertions(+), 12 deletions(-)

Comments

Laurent Pinchart March 2, 2019, 9:41 p.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

On Thu, Feb 28, 2019 at 09:04:03PM +0100, Jacopo Mondi wrote:
> Create video devices and subdevices associated with an ImgU unit, and
> link the entities in the media graph to prepare the device for capture
> operations at stream configuration time.
> 
> As we support a single stream at the moment, always select imgu0.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
>  1 file changed, 207 insertions(+), 12 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> index 4f1ab72debf8..9fa59c1bc97e 100644
> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> @@ -24,6 +24,28 @@ namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(IPU3)
>  
> +struct ImguDevice {

s/ImguDevice/ImgUDevice/ ?

> +	ImguDevice()
> +		: imgu(nullptr), input(nullptr), output(nullptr),
> +		  viewfinder(nullptr), stat(nullptr) {}

	{
	}

> +
> +	~ImguDevice()
> +	{
> +		delete imgu;
> +		delete input;
> +		delete output;
> +		delete viewfinder;
> +		delete stat;
> +	}

Unlike for the CIO2Device, this could be made a class, as I expect it
will have more code associated with it, and the ImgU instances will be
shared between the multiple camera instances. The linkImgu function
would be a good candidate.

> +
> +	V4L2Subdevice *imgu;
> +	V4L2Device *input;
> +	V4L2Device *output;
> +	V4L2Device *viewfinder;
> +	V4L2Device *stat;
> +	/* TODO: add param video device for 3A tuning */

\todo for consistency, even if this isn't a doxygen comment ?

> +};
> +
>  struct Cio2Device {
>  	Cio2Device()
>  		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
> @@ -44,6 +66,7 @@ class IPU3CameraData : public CameraData
>  {
>  public:
>  	Cio2Device cio2;
> +	ImguDevice *imgu;
>  
>  	Stream stream_;
>  };
> @@ -71,6 +94,10 @@ public:
>  	bool match(DeviceEnumerator *enumerator);
>  
>  private:
> +	static constexpr unsigned int IMGU_PAD_INPUT = 0;
> +	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
> +	static constexpr unsigned int IMGU_PAD_VF = 3;
> +	static constexpr unsigned int IMGU_PAD_STAT = 4;
>  	static constexpr unsigned int IPU3_BUF_NUM = 4;

I'd move that to the ImgUDevice struct/class. You can then remove the
IMGU_ prefix.

>  
>  	IPU3CameraData *cameraData(const Camera *camera)
> @@ -79,9 +106,17 @@ private:
>  			PipelineHandler::cameraData(camera));
>  	}
>  
> +	int linkImgu(ImguDevice *imgu);
> +
> +	V4L2Device *openDevice(MediaDevice *media, std::string &name);
> +	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
> +	int initImgu(ImguDevice *imgu);
>  	int initCio2(unsigned int index, Cio2Device *cio2);
>  	void registerCameras();
>  
> +	ImguDevice imgu0_;
> +	ImguDevice imgu1_;

Maybe an array with two entries ?

> +
>  	std::shared_ptr<MediaDevice> cio2MediaDev_;
>  	std::shared_ptr<MediaDevice> imguMediaDev_;
>  };
> @@ -159,6 +194,15 @@ int PipelineHandlerIPU3::configureStreams(Camera *camera,
>  	V4L2DeviceFormat devFormat = {};
>  	int ret;
>  
> +	/*
> +	 * TODO: dynamically assign ImgU devices; as of now, with a single
> +	 * stream supported, always use 'imgu0'.

	/**
	 * \todo

?

> +	 */
> +	data->imgu = &imgu0_;

How about moving this to camera creation time, and assigned imgu0 to the
first sensor and imgu1 to the second one ?

> +	ret = linkImgu(data->imgu);
> +	if (ret)
> +		return ret;
> +
>  	/*
>  	 * FIXME: as of now, the format gets applied to the sensor and is
>  	 * propagated along the pipeline. It should instead be applied on the
> @@ -334,17 +378,29 @@ bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
>  	if (cio2MediaDev_->open())
>  		goto error_release_mdev;
>  
> +	if (imguMediaDev_->open())
> +		goto error_close_mdev;
> +
>  	if (cio2MediaDev_->disableLinks())
> -		goto error_close_cio2;
> +		goto error_close_mdev;
> +
> +	if (initImgu(&imgu0_))
> +		goto error_close_mdev;
> +
> +	if (initImgu(&imgu1_))
> +		goto error_close_mdev;
> +
>  
>  	registerCameras();
>  
>  	cio2MediaDev_->close();
> +	imguMediaDev_->close();
>  
>  	return true;
>  
> -error_close_cio2:
> +error_close_mdev:
>  	cio2MediaDev_->close();
> +	imguMediaDev_->close();

Error handling will be simplified when you'll rebase. I'd go as far as
calling close() in the PipelineHandlerIPU3 destructor and remove the
error path here.

>  
>  error_release_mdev:
>  	cio2MediaDev_->release();
> @@ -353,6 +409,153 @@ error_release_mdev:
>  	return false;
>  }
>  
> +/* Link entities in the ImgU unit to prepare for capture operations. */
> +int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
> +{
> +	MediaLink *link;
> +	int ret;
> +
> +	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> +	std::string inputName = imguName + " input";
> +	std::string outputName = imguName + " output";
> +	std::string viewfinderName = imguName + " viewfinder";
> +	std::string statName = imguName + " 3a stat";
> +
> +	ret = imguMediaDev_->open();
> +	if (ret)
> +		return ret;
> +
> +	ret = imguMediaDev_->disableLinks();

This isn't a good idea, as you will interfere with the other ImgU. You
should enable/disable links selectively based on your needs.

> +	if (ret) {
> +		imguMediaDev_->close();
> +		return ret;
> +	}
> +
> +	/* Link entities to configure the IMGU unit for capture. */
> +	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
> +	if (!link) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get link '" << inputName << "':0 -> '"
> +			<< imguName << "':0";

Ideally you should use the IMGU_PAD_* constants instead of hardcoding
them in error messages.

> +		ret = -ENODEV;
> +		goto error_close_mediadev;
> +	}
> +	link->setEnabled(true);
> +
> +	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
> +	if (!link) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get link '" << imguName << "':2 -> '"
> +			<< outputName << "':0";
> +		ret = -ENODEV;
> +		goto error_close_mediadev;
> +	}
> +	link->setEnabled(true);
> +
> +	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
> +	if (!link) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get link '" << imguName << "':3 -> '"
> +			<< viewfinderName << "':0";
> +		ret = -ENODEV;
> +		goto error_close_mediadev;
> +	}
> +	link->setEnabled(true);

We have a single stream, why do you enable both output and viewfinder ?

> +
> +	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
> +	if (!link) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get link '" << imguName << "':4 -> '"
> +			<< statName << "':0";
> +		ret = -ENODEV;
> +		goto error_close_mediadev;
> +	}
> +	link->setEnabled(true);

Same here, we don't make use of stats yes, there's no need to enable
this link.

> +
> +	imguMediaDev_->close();
> +
> +	return 0;
> +
> +error_close_mediadev:
> +	imguMediaDev_->close();

We really really need to think about how to handle open/close and write
down a set of rules. Please make sure this is captured in a \todo.

> +
> +	return ret;
> +
> +}
> +
> +V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
> +					    std::string &name)

const std::string &

Same below.

> +{
> +	V4L2Device *dev;

You can move this line down to declare and initialize the variable at
the same time.

> +
> +	MediaEntity *entity = media->getEntityByName(name);
> +	if (!entity) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get entity '" << name << "'";
> +		return nullptr;
> +	}
> +
> +	dev = new V4L2Device(entity);
> +	if (dev->open())
> +		return nullptr;

You'll leak dev, a delete is needed.

> +
> +	return dev;
> +}
> +
> +V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
> +						  std::string &name)
> +{
> +	V4L2Subdevice *dev;
> +
> +	MediaEntity *entity = media->getEntityByName(name);
> +	if (!entity) {
> +		LOG(IPU3, Error)
> +			<< "Failed to get entity '" << name << "'";
> +		return nullptr;
> +	}
> +
> +	dev = new V4L2Subdevice(entity);
> +	if (dev->open())

Leak.

> +		return nullptr;
> +
> +	return dev;
> +}

These two functions may be candidates for future generic helpers. Could
you document them and add a \todo to mention this ?

> +
> +/* Create video devices and subdevices for the ImgU instance. */
> +int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
> +{
> +	unsigned int index = imgu == &imgu0_ ? 0 : 1;

A bit ugly, how about adding an index field to the ImguDevice structure
?

> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> +	std::string devName;
> +
> +	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
> +	if (!imgu->imgu)
> +		return -ENODEV;
> +
> +	devName = imguName + " input";
> +	imgu->input = openDevice(imguMediaDev_.get(), devName);
> +	if (!imgu->input)
> +		return -ENODEV;
> +
> +	devName = imguName + " output";
> +	imgu->output = openDevice(imguMediaDev_.get(), devName);
> +	if (!imgu->output)
> +		return -ENODEV;
> +
> +	devName = imguName + " viewfinder";
> +	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
> +	if (!imgu->viewfinder)
> +		return -ENODEV;
> +
> +	devName = imguName + " 3a stat";
> +	imgu->stat = openDevice(imguMediaDev_.get(), devName);

You could drop devName and call

	imgu->state = openDevice(imguMediaDev_.get(), imguName + " 3a stat");

Up to you.

> +	if (!imgu->stat)
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
>  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
>  {
>  	int ret;
> @@ -400,16 +603,8 @@ int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
>  		return ret;
>  
>  	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
> -	entity = cio2MediaDev_->getEntityByName(cio2Name);
> -	if (!entity) {
> -		LOG(IPU3, Error)
> -			<< "Failed to get entity '" << cio2Name << "'";
> -		return -EINVAL;
> -	}
> -
> -	cio2->output = new V4L2Device(entity);
> -	ret = cio2->output->open();
> -	if (ret)
> +	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
> +	if (!cio2->output)
>  		return ret;
>  
>  	return 0;
Jacopo Mondi March 9, 2019, 8:50 p.m. UTC | #2
Hi Laurent,

On Sat, Mar 02, 2019 at 11:41:29PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> On Thu, Feb 28, 2019 at 09:04:03PM +0100, Jacopo Mondi wrote:
> > Create video devices and subdevices associated with an ImgU unit, and
> > link the entities in the media graph to prepare the device for capture
> > operations at stream configuration time.
> >
> > As we support a single stream at the moment, always select imgu0.
> >
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > ---
> >  src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
> >  1 file changed, 207 insertions(+), 12 deletions(-)
> >
> > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > index 4f1ab72debf8..9fa59c1bc97e 100644
> > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > @@ -24,6 +24,28 @@ namespace libcamera {
> >
> >  LOG_DEFINE_CATEGORY(IPU3)
> >
> > +struct ImguDevice {
>
> s/ImguDevice/ImgUDevice/ ?

I would like ImguDevice best, but I'll use ImgUDevice for consistency
with the kernel documentation.

>
> > +	ImguDevice()
> > +		: imgu(nullptr), input(nullptr), output(nullptr),
> > +		  viewfinder(nullptr), stat(nullptr) {}
>
> 	{
> 	}
>
> > +
> > +	~ImguDevice()
> > +	{
> > +		delete imgu;
> > +		delete input;
> > +		delete output;
> > +		delete viewfinder;
> > +		delete stat;
> > +	}
>
> Unlike for the CIO2Device, this could be made a class, as I expect it
> will have more code associated with it, and the ImgU instances will be
> shared between the multiple camera instances. The linkImgu function
> would be a good candidate.
>

I'll move that to this class. I will has well add a "disableLinks()"
methods as you suggested below.

> > +
> > +	V4L2Subdevice *imgu;
> > +	V4L2Device *input;
> > +	V4L2Device *output;
> > +	V4L2Device *viewfinder;
> > +	V4L2Device *stat;
> > +	/* TODO: add param video device for 3A tuning */
>
> \todo for consistency, even if this isn't a doxygen comment ?
>

Will change all of these.

> > +};
> > +
> >  struct Cio2Device {
> >  	Cio2Device()
> >  		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
> > @@ -44,6 +66,7 @@ class IPU3CameraData : public CameraData
> >  {
> >  public:
> >  	Cio2Device cio2;
> > +	ImguDevice *imgu;
> >
> >  	Stream stream_;
> >  };
> > @@ -71,6 +94,10 @@ public:
> >  	bool match(DeviceEnumerator *enumerator);
> >
> >  private:
> > +	static constexpr unsigned int IMGU_PAD_INPUT = 0;
> > +	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
> > +	static constexpr unsigned int IMGU_PAD_VF = 3;
> > +	static constexpr unsigned int IMGU_PAD_STAT = 4;
> >  	static constexpr unsigned int IPU3_BUF_NUM = 4;
>
> I'd move that to the ImgUDevice struct/class. You can then remove the
> IMGU_ prefix.
>

Agreed.

> >
> >  	IPU3CameraData *cameraData(const Camera *camera)
> > @@ -79,9 +106,17 @@ private:
> >  			PipelineHandler::cameraData(camera));
> >  	}
> >
> > +	int linkImgu(ImguDevice *imgu);
> > +
> > +	V4L2Device *openDevice(MediaDevice *media, std::string &name);
> > +	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
> > +	int initImgu(ImguDevice *imgu);
> >  	int initCio2(unsigned int index, Cio2Device *cio2);
> >  	void registerCameras();
> >
> > +	ImguDevice imgu0_;
> > +	ImguDevice imgu1_;
>
> Maybe an array with two entries ?
>

They're two, and will stay two, but yes, I'll see how it looks.

> > +
> >  	std::shared_ptr<MediaDevice> cio2MediaDev_;
> >  	std::shared_ptr<MediaDevice> imguMediaDev_;
> >  };
> > @@ -159,6 +194,15 @@ int PipelineHandlerIPU3::configureStreams(Camera *camera,
> >  	V4L2DeviceFormat devFormat = {};
> >  	int ret;
> >
> > +	/*
> > +	 * TODO: dynamically assign ImgU devices; as of now, with a single
> > +	 * stream supported, always use 'imgu0'.
>
> 	/**
> 	 * \todo
>
> ?
>
> > +	 */
> > +	data->imgu = &imgu0_;
>
> How about moving this to camera creation time, and assigned imgu0 to the
> first sensor and imgu1 to the second one ?
>

I've put it here, as I assumed ImgU association with Cameras will
depend on the number of required streams. If you confirm my
understanding I would keep it here instead of moving it back and
forth.

> > +	ret = linkImgu(data->imgu);
> > +	if (ret)
> > +		return ret;
> > +
> >  	/*
> >  	 * FIXME: as of now, the format gets applied to the sensor and is
> >  	 * propagated along the pipeline. It should instead be applied on the
> > @@ -334,17 +378,29 @@ bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
> >  	if (cio2MediaDev_->open())
> >  		goto error_release_mdev;
> >
> > +	if (imguMediaDev_->open())
> > +		goto error_close_mdev;
> > +
> >  	if (cio2MediaDev_->disableLinks())
> > -		goto error_close_cio2;
> > +		goto error_close_mdev;
> > +
> > +	if (initImgu(&imgu0_))
> > +		goto error_close_mdev;
> > +
> > +	if (initImgu(&imgu1_))
> > +		goto error_close_mdev;
> > +
> >
> >  	registerCameras();
> >
> >  	cio2MediaDev_->close();
> > +	imguMediaDev_->close();
> >
> >  	return true;
> >
> > -error_close_cio2:
> > +error_close_mdev:
> >  	cio2MediaDev_->close();
> > +	imguMediaDev_->close();
>
> Error handling will be simplified when you'll rebase. I'd go as far as
> calling close() in the PipelineHandlerIPU3 destructor and remove the
> error path here.
>

It has been simplified by rebase, yes. I'll see how moving close() in
destructor looks like (I think it's good)

> >
> >  error_release_mdev:
> >  	cio2MediaDev_->release();
> > @@ -353,6 +409,153 @@ error_release_mdev:
> >  	return false;
> >  }
> >
> > +/* Link entities in the ImgU unit to prepare for capture operations. */
> > +int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
> > +{
> > +	MediaLink *link;
> > +	int ret;
> > +
> > +	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
> > +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> > +	std::string inputName = imguName + " input";
> > +	std::string outputName = imguName + " output";
> > +	std::string viewfinderName = imguName + " viewfinder";
> > +	std::string statName = imguName + " 3a stat";
> > +
> > +	ret = imguMediaDev_->open();
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = imguMediaDev_->disableLinks();
>
> This isn't a good idea, as you will interfere with the other ImgU. You
> should enable/disable links selectively based on your needs.
>

As I've said, I'll make a per-imgu instance link disabling method. I
hope it is enough to disable all links in an imgu instance and we
don't have to go link by link...

> > +	if (ret) {
> > +		imguMediaDev_->close();
> > +		return ret;
> > +	}
> > +
> > +	/* Link entities to configure the IMGU unit for capture. */
> > +	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
> > +	if (!link) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get link '" << inputName << "':0 -> '"
> > +			<< imguName << "':0";
>
> Ideally you should use the IMGU_PAD_* constants instead of hardcoding
> them in error messages.
>

Ah well, it's just more lines for a single printout, but ok.

> > +		ret = -ENODEV;
> > +		goto error_close_mediadev;
> > +	}
> > +	link->setEnabled(true);
> > +
> > +	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
> > +	if (!link) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get link '" << imguName << "':2 -> '"
> > +			<< outputName << "':0";
> > +		ret = -ENODEV;
> > +		goto error_close_mediadev;
> > +	}
> > +	link->setEnabled(true);
> > +
> > +	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
> > +	if (!link) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get link '" << imguName << "':3 -> '"
> > +			<< viewfinderName << "':0";
> > +		ret = -ENODEV;
> > +		goto error_close_mediadev;
> > +	}
> > +	link->setEnabled(true);
>
> We have a single stream, why do you enable both output and viewfinder ?
>

From my testings, if I keep them disabled (== not linked) the system
hangs even if I'm only capturing from the ouput video device. I will
do some more testing, as setting up those nodes, queuing and dequeuing
buffers there requires some not-nice code to keep track of the buffer
indexes, as you've noticed.

> > +
> > +	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
> > +	if (!link) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get link '" << imguName << "':4 -> '"
> > +			<< statName << "':0";
> > +		ret = -ENODEV;
> > +		goto error_close_mediadev;
> > +	}
> > +	link->setEnabled(true);
>
> Same here, we don't make use of stats yes, there's no need to enable
> this link.
>
> > +
> > +	imguMediaDev_->close();
> > +
> > +	return 0;
> > +
> > +error_close_mediadev:
> > +	imguMediaDev_->close();
>
> We really really need to think about how to handle open/close and write
> down a set of rules. Please make sure this is captured in a \todo.
>
> > +
> > +	return ret;
> > +
> > +}
> > +
> > +V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
> > +					    std::string &name)
>
> const std::string &
>
> Same below.
>
> > +{
> > +	V4L2Device *dev;
>
> You can move this line down to declare and initialize the variable at
> the same time.
>
> > +
> > +	MediaEntity *entity = media->getEntityByName(name);
> > +	if (!entity) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get entity '" << name << "'";
> > +		return nullptr;
> > +	}
> > +
> > +	dev = new V4L2Device(entity);
> > +	if (dev->open())
> > +		return nullptr;
>
> You'll leak dev, a delete is needed.
>

Ah, right! will fix.

> > +
> > +	return dev;
> > +}
> > +
> > +V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
> > +						  std::string &name)
> > +{
> > +	V4L2Subdevice *dev;
> > +
> > +	MediaEntity *entity = media->getEntityByName(name);
> > +	if (!entity) {
> > +		LOG(IPU3, Error)
> > +			<< "Failed to get entity '" << name << "'";
> > +		return nullptr;
> > +	}
> > +
> > +	dev = new V4L2Subdevice(entity);
> > +	if (dev->open())
>
> Leak.
>
> > +		return nullptr;
> > +
> > +	return dev;
> > +}
>
> These two functions may be candidates for future generic helpers. Could
> you document them and add a \todo to mention this ?
>

Agreed!

> > +
> > +/* Create video devices and subdevices for the ImgU instance. */
> > +int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
> > +{
> > +	unsigned int index = imgu == &imgu0_ ? 0 : 1;
>
> A bit ugly, how about adding an index field to the ImguDevice structure
> ?

Yes. But when would you intialize such a field? at match time? In this
function? I'll see how it'll look

>
> > +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> > +	std::string devName;
> > +
> > +	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
> > +	if (!imgu->imgu)
> > +		return -ENODEV;
> > +
> > +	devName = imguName + " input";
> > +	imgu->input = openDevice(imguMediaDev_.get(), devName);
> > +	if (!imgu->input)
> > +		return -ENODEV;
> > +
> > +	devName = imguName + " output";
> > +	imgu->output = openDevice(imguMediaDev_.get(), devName);
> > +	if (!imgu->output)
> > +		return -ENODEV;
> > +
> > +	devName = imguName + " viewfinder";
> > +	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
> > +	if (!imgu->viewfinder)
> > +		return -ENODEV;
> > +
> > +	devName = imguName + " 3a stat";
> > +	imgu->stat = openDevice(imguMediaDev_.get(), devName);
>
> You could drop devName and call
>
> 	imgu->state = openDevice(imguMediaDev_.get(), imguName + " 3a stat");
>
> Up to you.

Less lines, so it's better

Thanks
  j

>
> > +	if (!imgu->stat)
> > +		return -ENODEV;
> > +
> > +	return 0;
> > +}
> > +
> >  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >  {
> >  	int ret;
> > @@ -400,16 +603,8 @@ int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >  		return ret;
> >
> >  	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
> > -	entity = cio2MediaDev_->getEntityByName(cio2Name);
> > -	if (!entity) {
> > -		LOG(IPU3, Error)
> > -			<< "Failed to get entity '" << cio2Name << "'";
> > -		return -EINVAL;
> > -	}
> > -
> > -	cio2->output = new V4L2Device(entity);
> > -	ret = cio2->output->open();
> > -	if (ret)
> > +	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
> > +	if (!cio2->output)
> >  		return ret;
> >
> >  	return 0;
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart March 10, 2019, 1:08 p.m. UTC | #3
Hi Jacopo,

On Sat, Mar 09, 2019 at 09:50:18PM +0100, Jacopo Mondi wrote:
> On Sat, Mar 02, 2019 at 11:41:29PM +0200, Laurent Pinchart wrote:
> > On Thu, Feb 28, 2019 at 09:04:03PM +0100, Jacopo Mondi wrote:
> >> Create video devices and subdevices associated with an ImgU unit, and
> >> link the entities in the media graph to prepare the device for capture
> >> operations at stream configuration time.
> >>
> >> As we support a single stream at the moment, always select imgu0.
> >>
> >> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> >> ---
> >>  src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
> >>  1 file changed, 207 insertions(+), 12 deletions(-)
> >>
> >> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> >> index 4f1ab72debf8..9fa59c1bc97e 100644
> >> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> >> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> >> @@ -24,6 +24,28 @@ namespace libcamera {
> >>
> >>  LOG_DEFINE_CATEGORY(IPU3)
> >>
> >> +struct ImguDevice {
> >
> > s/ImguDevice/ImgUDevice/ ?
> 
> I would like ImguDevice best, but I'll use ImgUDevice for consistency
> with the kernel documentation.
> 
> >> +	ImguDevice()
> >> +		: imgu(nullptr), input(nullptr), output(nullptr),
> >> +		  viewfinder(nullptr), stat(nullptr) {}
> >
> > 	{
> > 	}
> >
> >> +
> >> +	~ImguDevice()
> >> +	{
> >> +		delete imgu;
> >> +		delete input;
> >> +		delete output;
> >> +		delete viewfinder;
> >> +		delete stat;
> >> +	}
> >
> > Unlike for the CIO2Device, this could be made a class, as I expect it
> > will have more code associated with it, and the ImgU instances will be
> > shared between the multiple camera instances. The linkImgu function
> > would be a good candidate.
> 
> I'll move that to this class. I will has well add a "disableLinks()"
> methods as you suggested below.
> 
> >> +
> >> +	V4L2Subdevice *imgu;
> >> +	V4L2Device *input;
> >> +	V4L2Device *output;
> >> +	V4L2Device *viewfinder;
> >> +	V4L2Device *stat;
> >> +	/* TODO: add param video device for 3A tuning */
> >
> > \todo for consistency, even if this isn't a doxygen comment ?
> 
> Will change all of these.
> 
> >> +};
> >> +
> >>  struct Cio2Device {
> >>  	Cio2Device()
> >>  		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
> >> @@ -44,6 +66,7 @@ class IPU3CameraData : public CameraData
> >>  {
> >>  public:
> >>  	Cio2Device cio2;
> >> +	ImguDevice *imgu;
> >>
> >>  	Stream stream_;
> >>  };
> >> @@ -71,6 +94,10 @@ public:
> >>  	bool match(DeviceEnumerator *enumerator);
> >>
> >>  private:
> >> +	static constexpr unsigned int IMGU_PAD_INPUT = 0;
> >> +	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
> >> +	static constexpr unsigned int IMGU_PAD_VF = 3;
> >> +	static constexpr unsigned int IMGU_PAD_STAT = 4;
> >>  	static constexpr unsigned int IPU3_BUF_NUM = 4;
> >
> > I'd move that to the ImgUDevice struct/class. You can then remove the
> > IMGU_ prefix.
> 
> Agreed.
> 
> >>
> >>  	IPU3CameraData *cameraData(const Camera *camera)
> >> @@ -79,9 +106,17 @@ private:
> >>  			PipelineHandler::cameraData(camera));
> >>  	}
> >>
> >> +	int linkImgu(ImguDevice *imgu);
> >> +
> >> +	V4L2Device *openDevice(MediaDevice *media, std::string &name);
> >> +	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
> >> +	int initImgu(ImguDevice *imgu);
> >>  	int initCio2(unsigned int index, Cio2Device *cio2);
> >>  	void registerCameras();
> >>
> >> +	ImguDevice imgu0_;
> >> +	ImguDevice imgu1_;
> >
> > Maybe an array with two entries ?
> 
> They're two, and will stay two, but yes, I'll see how it looks.
> 
> >> +
> >>  	std::shared_ptr<MediaDevice> cio2MediaDev_;
> >>  	std::shared_ptr<MediaDevice> imguMediaDev_;
> >>  };
> >> @@ -159,6 +194,15 @@ int PipelineHandlerIPU3::configureStreams(Camera *camera,
> >>  	V4L2DeviceFormat devFormat = {};
> >>  	int ret;
> >>
> >> +	/*
> >> +	 * TODO: dynamically assign ImgU devices; as of now, with a single
> >> +	 * stream supported, always use 'imgu0'.
> >
> > 	/**
> > 	 * \todo
> >
> > ?
> >
> >> +	 */
> >> +	data->imgu = &imgu0_;
> >
> > How about moving this to camera creation time, and assigned imgu0 to the
> > first sensor and imgu1 to the second one ?
> >
> 
> I've put it here, as I assumed ImgU association with Cameras will
> depend on the number of required streams. If you confirm my
> understanding I would keep it here instead of moving it back and
> forth.

Your understanding is correct, but with this implementation imgu0 is
used for both cameras, so we can only use one camera at a time. If we
assign imgu0 to the first camera and imgu1 to the second one we can
start using both cameras, with up to two streams each, and later expand
that to more streams.

> >> +	ret = linkImgu(data->imgu);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >>  	/*
> >>  	 * FIXME: as of now, the format gets applied to the sensor and is
> >>  	 * propagated along the pipeline. It should instead be applied on the
> >> @@ -334,17 +378,29 @@ bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
> >>  	if (cio2MediaDev_->open())
> >>  		goto error_release_mdev;
> >>
> >> +	if (imguMediaDev_->open())
> >> +		goto error_close_mdev;
> >> +
> >>  	if (cio2MediaDev_->disableLinks())
> >> -		goto error_close_cio2;
> >> +		goto error_close_mdev;
> >> +
> >> +	if (initImgu(&imgu0_))
> >> +		goto error_close_mdev;
> >> +
> >> +	if (initImgu(&imgu1_))
> >> +		goto error_close_mdev;
> >> +
> >>
> >>  	registerCameras();
> >>
> >>  	cio2MediaDev_->close();
> >> +	imguMediaDev_->close();
> >>
> >>  	return true;
> >>
> >> -error_close_cio2:
> >> +error_close_mdev:
> >>  	cio2MediaDev_->close();
> >> +	imguMediaDev_->close();
> >
> > Error handling will be simplified when you'll rebase. I'd go as far as
> > calling close() in the PipelineHandlerIPU3 destructor and remove the
> > error path here.
> 
> It has been simplified by rebase, yes. I'll see how moving close() in
> destructor looks like (I think it's good)
> 
> >>
> >>  error_release_mdev:
> >>  	cio2MediaDev_->release();
> >> @@ -353,6 +409,153 @@ error_release_mdev:
> >>  	return false;
> >>  }
> >>
> >> +/* Link entities in the ImgU unit to prepare for capture operations. */
> >> +int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
> >> +{
> >> +	MediaLink *link;
> >> +	int ret;
> >> +
> >> +	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
> >> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> >> +	std::string inputName = imguName + " input";
> >> +	std::string outputName = imguName + " output";
> >> +	std::string viewfinderName = imguName + " viewfinder";
> >> +	std::string statName = imguName + " 3a stat";
> >> +
> >> +	ret = imguMediaDev_->open();
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = imguMediaDev_->disableLinks();
> >
> > This isn't a good idea, as you will interfere with the other ImgU. You
> > should enable/disable links selectively based on your needs.
> 
> As I've said, I'll make a per-imgu instance link disabling method. I
> hope it is enough to disable all links in an imgu instance and we
> don't have to go link by link...

I haven't checked recently, are the two ImgUs exposed as separate media
devices ? If so that's fine, otherwise you'll have to go link by link.

> >> +	if (ret) {
> >> +		imguMediaDev_->close();
> >> +		return ret;
> >> +	}
> >> +
> >> +	/* Link entities to configure the IMGU unit for capture. */
> >> +	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
> >> +	if (!link) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get link '" << inputName << "':0 -> '"
> >> +			<< imguName << "':0";
> >
> > Ideally you should use the IMGU_PAD_* constants instead of hardcoding
> > them in error messages.
> 
> Ah well, it's just more lines for a single printout, but ok.
> 
> >> +		ret = -ENODEV;
> >> +		goto error_close_mediadev;
> >> +	}
> >> +	link->setEnabled(true);
> >> +
> >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
> >> +	if (!link) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get link '" << imguName << "':2 -> '"
> >> +			<< outputName << "':0";
> >> +		ret = -ENODEV;
> >> +		goto error_close_mediadev;
> >> +	}
> >> +	link->setEnabled(true);
> >> +
> >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
> >> +	if (!link) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get link '" << imguName << "':3 -> '"
> >> +			<< viewfinderName << "':0";
> >> +		ret = -ENODEV;
> >> +		goto error_close_mediadev;
> >> +	}
> >> +	link->setEnabled(true);
> >
> > We have a single stream, why do you enable both output and viewfinder ?
> 
> From my testings, if I keep them disabled (== not linked) the system
> hangs even if I'm only capturing from the ouput video device. I will
> do some more testing, as setting up those nodes, queuing and dequeuing
> buffers there requires some not-nice code to keep track of the buffer
> indexes, as you've noticed.

Another kernel bug fix candidate :-)

> >> +
> >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
> >> +	if (!link) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get link '" << imguName << "':4 -> '"
> >> +			<< statName << "':0";
> >> +		ret = -ENODEV;
> >> +		goto error_close_mediadev;
> >> +	}
> >> +	link->setEnabled(true);
> >
> > Same here, we don't make use of stats yes, there's no need to enable
> > this link.
> >
> >> +
> >> +	imguMediaDev_->close();
> >> +
> >> +	return 0;
> >> +
> >> +error_close_mediadev:
> >> +	imguMediaDev_->close();
> >
> > We really really need to think about how to handle open/close and write
> > down a set of rules. Please make sure this is captured in a \todo.
> >
> >> +
> >> +	return ret;
> >> +
> >> +}
> >> +
> >> +V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
> >> +					    std::string &name)
> >
> > const std::string &
> >
> > Same below.
> >
> >> +{
> >> +	V4L2Device *dev;
> >
> > You can move this line down to declare and initialize the variable at
> > the same time.
> >
> >> +
> >> +	MediaEntity *entity = media->getEntityByName(name);
> >> +	if (!entity) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get entity '" << name << "'";
> >> +		return nullptr;
> >> +	}
> >> +
> >> +	dev = new V4L2Device(entity);
> >> +	if (dev->open())
> >> +		return nullptr;
> >
> > You'll leak dev, a delete is needed.
> 
> Ah, right! will fix.
> 
> >> +
> >> +	return dev;
> >> +}
> >> +
> >> +V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
> >> +						  std::string &name)
> >> +{
> >> +	V4L2Subdevice *dev;
> >> +
> >> +	MediaEntity *entity = media->getEntityByName(name);
> >> +	if (!entity) {
> >> +		LOG(IPU3, Error)
> >> +			<< "Failed to get entity '" << name << "'";
> >> +		return nullptr;
> >> +	}
> >> +
> >> +	dev = new V4L2Subdevice(entity);
> >> +	if (dev->open())
> >
> > Leak.
> >
> >> +		return nullptr;
> >> +
> >> +	return dev;
> >> +}
> >
> > These two functions may be candidates for future generic helpers. Could
> > you document them and add a \todo to mention this ?
> 
> Agreed!
> 
> >> +
> >> +/* Create video devices and subdevices for the ImgU instance. */
> >> +int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
> >> +{
> >> +	unsigned int index = imgu == &imgu0_ ? 0 : 1;
> >
> > A bit ugly, how about adding an index field to the ImguDevice structure
> > ?
> 
> Yes. But when would you intialize such a field? at match time? In this
> function? I'll see how it'll look

I think I'd do it at match time.

> >> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> >> +	std::string devName;
> >> +
> >> +	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
> >> +	if (!imgu->imgu)
> >> +		return -ENODEV;
> >> +
> >> +	devName = imguName + " input";
> >> +	imgu->input = openDevice(imguMediaDev_.get(), devName);
> >> +	if (!imgu->input)
> >> +		return -ENODEV;
> >> +
> >> +	devName = imguName + " output";
> >> +	imgu->output = openDevice(imguMediaDev_.get(), devName);
> >> +	if (!imgu->output)
> >> +		return -ENODEV;
> >> +
> >> +	devName = imguName + " viewfinder";
> >> +	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
> >> +	if (!imgu->viewfinder)
> >> +		return -ENODEV;
> >> +
> >> +	devName = imguName + " 3a stat";
> >> +	imgu->stat = openDevice(imguMediaDev_.get(), devName);
> >
> > You could drop devName and call
> >
> > 	imgu->state = openDevice(imguMediaDev_.get(), imguName + " 3a stat");
> >
> > Up to you.
> 
> Less lines, so it's better
> 
> >> +	if (!imgu->stat)
> >> +		return -ENODEV;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >>  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >>  {
> >>  	int ret;
> >> @@ -400,16 +603,8 @@ int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >>  		return ret;
> >>
> >>  	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
> >> -	entity = cio2MediaDev_->getEntityByName(cio2Name);
> >> -	if (!entity) {
> >> -		LOG(IPU3, Error)
> >> -			<< "Failed to get entity '" << cio2Name << "'";
> >> -		return -EINVAL;
> >> -	}
> >> -
> >> -	cio2->output = new V4L2Device(entity);
> >> -	ret = cio2->output->open();
> >> -	if (ret)
> >> +	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
> >> +	if (!cio2->output)
> >>  		return ret;
> >>
> >>  	return 0;
Jacopo Mondi March 11, 2019, 8:10 a.m. UTC | #4
Hi Laurent,

On Sun, Mar 10, 2019 at 03:08:33PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Sat, Mar 09, 2019 at 09:50:18PM +0100, Jacopo Mondi wrote:
> > On Sat, Mar 02, 2019 at 11:41:29PM +0200, Laurent Pinchart wrote:
> > > On Thu, Feb 28, 2019 at 09:04:03PM +0100, Jacopo Mondi wrote:
> > >> Create video devices and subdevices associated with an ImgU unit, and
> > >> link the entities in the media graph to prepare the device for capture
> > >> operations at stream configuration time.
> > >>
> > >> As we support a single stream at the moment, always select imgu0.
> > >>
> > >> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > >> ---
> > >>  src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
> > >>  1 file changed, 207 insertions(+), 12 deletions(-)
> > >>
> > >> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > >> index 4f1ab72debf8..9fa59c1bc97e 100644
> > >> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> > >> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> > >> @@ -24,6 +24,28 @@ namespace libcamera {
> > >>
> > >>  LOG_DEFINE_CATEGORY(IPU3)
> > >>
> > >> +struct ImguDevice {
> > >
> > > s/ImguDevice/ImgUDevice/ ?
> >
> > I would like ImguDevice best, but I'll use ImgUDevice for consistency
> > with the kernel documentation.
> >
> > >> +	ImguDevice()
> > >> +		: imgu(nullptr), input(nullptr), output(nullptr),
> > >> +		  viewfinder(nullptr), stat(nullptr) {}
> > >
> > > 	{
> > > 	}
> > >
> > >> +
> > >> +	~ImguDevice()
> > >> +	{
> > >> +		delete imgu;
> > >> +		delete input;
> > >> +		delete output;
> > >> +		delete viewfinder;
> > >> +		delete stat;
> > >> +	}
> > >
> > > Unlike for the CIO2Device, this could be made a class, as I expect it
> > > will have more code associated with it, and the ImgU instances will be
> > > shared between the multiple camera instances. The linkImgu function
> > > would be a good candidate.
> >
> > I'll move that to this class. I will has well add a "disableLinks()"
> > methods as you suggested below.
> >
> > >> +
> > >> +	V4L2Subdevice *imgu;
> > >> +	V4L2Device *input;
> > >> +	V4L2Device *output;
> > >> +	V4L2Device *viewfinder;
> > >> +	V4L2Device *stat;
> > >> +	/* TODO: add param video device for 3A tuning */
> > >
> > > \todo for consistency, even if this isn't a doxygen comment ?
> >
> > Will change all of these.
> >
> > >> +};
> > >> +
> > >>  struct Cio2Device {
> > >>  	Cio2Device()
> > >>  		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
> > >> @@ -44,6 +66,7 @@ class IPU3CameraData : public CameraData
> > >>  {
> > >>  public:
> > >>  	Cio2Device cio2;
> > >> +	ImguDevice *imgu;
> > >>
> > >>  	Stream stream_;
> > >>  };
> > >> @@ -71,6 +94,10 @@ public:
> > >>  	bool match(DeviceEnumerator *enumerator);
> > >>
> > >>  private:
> > >> +	static constexpr unsigned int IMGU_PAD_INPUT = 0;
> > >> +	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
> > >> +	static constexpr unsigned int IMGU_PAD_VF = 3;
> > >> +	static constexpr unsigned int IMGU_PAD_STAT = 4;
> > >>  	static constexpr unsigned int IPU3_BUF_NUM = 4;
> > >
> > > I'd move that to the ImgUDevice struct/class. You can then remove the
> > > IMGU_ prefix.
> >
> > Agreed.
> >
> > >>
> > >>  	IPU3CameraData *cameraData(const Camera *camera)
> > >> @@ -79,9 +106,17 @@ private:
> > >>  			PipelineHandler::cameraData(camera));
> > >>  	}
> > >>
> > >> +	int linkImgu(ImguDevice *imgu);
> > >> +
> > >> +	V4L2Device *openDevice(MediaDevice *media, std::string &name);
> > >> +	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
> > >> +	int initImgu(ImguDevice *imgu);
> > >>  	int initCio2(unsigned int index, Cio2Device *cio2);
> > >>  	void registerCameras();
> > >>
> > >> +	ImguDevice imgu0_;
> > >> +	ImguDevice imgu1_;
> > >
> > > Maybe an array with two entries ?
> >
> > They're two, and will stay two, but yes, I'll see how it looks.
> >
> > >> +
> > >>  	std::shared_ptr<MediaDevice> cio2MediaDev_;
> > >>  	std::shared_ptr<MediaDevice> imguMediaDev_;
> > >>  };
> > >> @@ -159,6 +194,15 @@ int PipelineHandlerIPU3::configureStreams(Camera *camera,
> > >>  	V4L2DeviceFormat devFormat = {};
> > >>  	int ret;
> > >>
> > >> +	/*
> > >> +	 * TODO: dynamically assign ImgU devices; as of now, with a single
> > >> +	 * stream supported, always use 'imgu0'.
> > >
> > > 	/**
> > > 	 * \todo
> > >
> > > ?
> > >
> > >> +	 */
> > >> +	data->imgu = &imgu0_;
> > >
> > > How about moving this to camera creation time, and assigned imgu0 to the
> > > first sensor and imgu1 to the second one ?
> > >
> >
> > I've put it here, as I assumed ImgU association with Cameras will
> > depend on the number of required streams. If you confirm my
> > understanding I would keep it here instead of moving it back and
> > forth.
>
> Your understanding is correct, but with this implementation imgu0 is
> used for both cameras, so we can only use one camera at a time. If we
> assign imgu0 to the first camera and imgu1 to the second one we can
> start using both cameras, with up to two streams each, and later expand
> that to more streams.
>

I see. I'll do the assignement there (and limit the number of
supported cameras to two for now)

> > >> +	ret = linkImgu(data->imgu);
> > >> +	if (ret)
> > >> +		return ret;
> > >> +
> > >>  	/*
> > >>  	 * FIXME: as of now, the format gets applied to the sensor and is
> > >>  	 * propagated along the pipeline. It should instead be applied on the
> > >> @@ -334,17 +378,29 @@ bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
> > >>  	if (cio2MediaDev_->open())
> > >>  		goto error_release_mdev;
> > >>
> > >> +	if (imguMediaDev_->open())
> > >> +		goto error_close_mdev;
> > >> +
> > >>  	if (cio2MediaDev_->disableLinks())
> > >> -		goto error_close_cio2;
> > >> +		goto error_close_mdev;
> > >> +
> > >> +	if (initImgu(&imgu0_))
> > >> +		goto error_close_mdev;
> > >> +
> > >> +	if (initImgu(&imgu1_))
> > >> +		goto error_close_mdev;
> > >> +
> > >>
> > >>  	registerCameras();
> > >>
> > >>  	cio2MediaDev_->close();
> > >> +	imguMediaDev_->close();
> > >>
> > >>  	return true;
> > >>
> > >> -error_close_cio2:
> > >> +error_close_mdev:
> > >>  	cio2MediaDev_->close();
> > >> +	imguMediaDev_->close();
> > >
> > > Error handling will be simplified when you'll rebase. I'd go as far as
> > > calling close() in the PipelineHandlerIPU3 destructor and remove the
> > > error path here.
> >
> > It has been simplified by rebase, yes. I'll see how moving close() in
> > destructor looks like (I think it's good)
> >

Actually, the pipeline handler will be destroyed at library tear-down
time, isn't it? So I don't think we can rely on its destructor to close the
media devices...

> > >>
> > >>  error_release_mdev:
> > >>  	cio2MediaDev_->release();
> > >> @@ -353,6 +409,153 @@ error_release_mdev:
> > >>  	return false;
> > >>  }
> > >>
> > >> +/* Link entities in the ImgU unit to prepare for capture operations. */
> > >> +int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
> > >> +{
> > >> +	MediaLink *link;
> > >> +	int ret;
> > >> +
> > >> +	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
> > >> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> > >> +	std::string inputName = imguName + " input";
> > >> +	std::string outputName = imguName + " output";
> > >> +	std::string viewfinderName = imguName + " viewfinder";
> > >> +	std::string statName = imguName + " 3a stat";
> > >> +
> > >> +	ret = imguMediaDev_->open();
> > >> +	if (ret)
> > >> +		return ret;
> > >> +
> > >> +	ret = imguMediaDev_->disableLinks();
> > >
> > > This isn't a good idea, as you will interfere with the other ImgU. You
> > > should enable/disable links selectively based on your needs.
> >
> > As I've said, I'll make a per-imgu instance link disabling method. I
> > hope it is enough to disable all links in an imgu instance and we
> > don't have to go link by link...
>
> I haven't checked recently, are the two ImgUs exposed as separate media
> devices ? If so that's fine, otherwise you'll have to go link by link.
>

No, the imgu units are part of the same media device. But it's fine
anyway, I will create a method that goes link by link :(

> > >> +	if (ret) {
> > >> +		imguMediaDev_->close();
> > >> +		return ret;
> > >> +	}
> > >> +
> > >> +	/* Link entities to configure the IMGU unit for capture. */
> > >> +	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
> > >> +	if (!link) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get link '" << inputName << "':0 -> '"
> > >> +			<< imguName << "':0";
> > >
> > > Ideally you should use the IMGU_PAD_* constants instead of hardcoding
> > > them in error messages.
> >
> > Ah well, it's just more lines for a single printout, but ok.
> >
> > >> +		ret = -ENODEV;
> > >> +		goto error_close_mediadev;
> > >> +	}
> > >> +	link->setEnabled(true);
> > >> +
> > >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
> > >> +	if (!link) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get link '" << imguName << "':2 -> '"
> > >> +			<< outputName << "':0";
> > >> +		ret = -ENODEV;
> > >> +		goto error_close_mediadev;
> > >> +	}
> > >> +	link->setEnabled(true);
> > >> +
> > >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
> > >> +	if (!link) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get link '" << imguName << "':3 -> '"
> > >> +			<< viewfinderName << "':0";
> > >> +		ret = -ENODEV;
> > >> +		goto error_close_mediadev;
> > >> +	}
> > >> +	link->setEnabled(true);
> > >
> > > We have a single stream, why do you enable both output and viewfinder ?
> >
> > From my testings, if I keep them disabled (== not linked) the system
> > hangs even if I'm only capturing from the ouput video device. I will
> > do some more testing, as setting up those nodes, queuing and dequeuing
> > buffers there requires some not-nice code to keep track of the buffer
> > indexes, as you've noticed.
>
> Another kernel bug fix candidate :-)
>

I plan to do more testing once I have included all these comments in
v2. Will report how they go..

Thanks
  j

> > >> +
> > >> +	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
> > >> +	if (!link) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get link '" << imguName << "':4 -> '"
> > >> +			<< statName << "':0";
> > >> +		ret = -ENODEV;
> > >> +		goto error_close_mediadev;
> > >> +	}
> > >> +	link->setEnabled(true);
> > >
> > > Same here, we don't make use of stats yes, there's no need to enable
> > > this link.
> > >
> > >> +
> > >> +	imguMediaDev_->close();
> > >> +
> > >> +	return 0;
> > >> +
> > >> +error_close_mediadev:
> > >> +	imguMediaDev_->close();
> > >
> > > We really really need to think about how to handle open/close and write
> > > down a set of rules. Please make sure this is captured in a \todo.
> > >
> > >> +
> > >> +	return ret;
> > >> +
> > >> +}
> > >> +
> > >> +V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
> > >> +					    std::string &name)
> > >
> > > const std::string &
> > >
> > > Same below.
> > >
> > >> +{
> > >> +	V4L2Device *dev;
> > >
> > > You can move this line down to declare and initialize the variable at
> > > the same time.
> > >
> > >> +
> > >> +	MediaEntity *entity = media->getEntityByName(name);
> > >> +	if (!entity) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get entity '" << name << "'";
> > >> +		return nullptr;
> > >> +	}
> > >> +
> > >> +	dev = new V4L2Device(entity);
> > >> +	if (dev->open())
> > >> +		return nullptr;
> > >
> > > You'll leak dev, a delete is needed.
> >
> > Ah, right! will fix.
> >
> > >> +
> > >> +	return dev;
> > >> +}
> > >> +
> > >> +V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
> > >> +						  std::string &name)
> > >> +{
> > >> +	V4L2Subdevice *dev;
> > >> +
> > >> +	MediaEntity *entity = media->getEntityByName(name);
> > >> +	if (!entity) {
> > >> +		LOG(IPU3, Error)
> > >> +			<< "Failed to get entity '" << name << "'";
> > >> +		return nullptr;
> > >> +	}
> > >> +
> > >> +	dev = new V4L2Subdevice(entity);
> > >> +	if (dev->open())
> > >
> > > Leak.
> > >
> > >> +		return nullptr;
> > >> +
> > >> +	return dev;
> > >> +}
> > >
> > > These two functions may be candidates for future generic helpers. Could
> > > you document them and add a \todo to mention this ?
> >
> > Agreed!
> >
> > >> +
> > >> +/* Create video devices and subdevices for the ImgU instance. */
> > >> +int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
> > >> +{
> > >> +	unsigned int index = imgu == &imgu0_ ? 0 : 1;
> > >
> > > A bit ugly, how about adding an index field to the ImguDevice structure
> > > ?
> >
> > Yes. But when would you intialize such a field? at match time? In this
> > function? I'll see how it'll look
>
> I think I'd do it at match time.
>
> > >> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> > >> +	std::string devName;
> > >> +
> > >> +	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
> > >> +	if (!imgu->imgu)
> > >> +		return -ENODEV;
> > >> +
> > >> +	devName = imguName + " input";
> > >> +	imgu->input = openDevice(imguMediaDev_.get(), devName);
> > >> +	if (!imgu->input)
> > >> +		return -ENODEV;
> > >> +
> > >> +	devName = imguName + " output";
> > >> +	imgu->output = openDevice(imguMediaDev_.get(), devName);
> > >> +	if (!imgu->output)
> > >> +		return -ENODEV;
> > >> +
> > >> +	devName = imguName + " viewfinder";
> > >> +	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
> > >> +	if (!imgu->viewfinder)
> > >> +		return -ENODEV;
> > >> +
> > >> +	devName = imguName + " 3a stat";
> > >> +	imgu->stat = openDevice(imguMediaDev_.get(), devName);
> > >
> > > You could drop devName and call
> > >
> > > 	imgu->state = openDevice(imguMediaDev_.get(), imguName + " 3a stat");
> > >
> > > Up to you.
> >
> > Less lines, so it's better
> >
> > >> +	if (!imgu->stat)
> > >> +		return -ENODEV;
> > >> +
> > >> +	return 0;
> > >> +}
> > >> +
> > >>  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> > >>  {
> > >>  	int ret;
> > >> @@ -400,16 +603,8 @@ int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> > >>  		return ret;
> > >>
> > >>  	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
> > >> -	entity = cio2MediaDev_->getEntityByName(cio2Name);
> > >> -	if (!entity) {
> > >> -		LOG(IPU3, Error)
> > >> -			<< "Failed to get entity '" << cio2Name << "'";
> > >> -		return -EINVAL;
> > >> -	}
> > >> -
> > >> -	cio2->output = new V4L2Device(entity);
> > >> -	ret = cio2->output->open();
> > >> -	if (ret)
> > >> +	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
> > >> +	if (!cio2->output)
> > >>  		return ret;
> > >>
> > >>  	return 0;
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart March 11, 2019, 8:33 a.m. UTC | #5
Hi Jacopo,

On Mon, Mar 11, 2019 at 09:10:41AM +0100, Jacopo Mondi wrote:
> On Sun, Mar 10, 2019 at 03:08:33PM +0200, Laurent Pinchart wrote:
> > On Sat, Mar 09, 2019 at 09:50:18PM +0100, Jacopo Mondi wrote:
> >> On Sat, Mar 02, 2019 at 11:41:29PM +0200, Laurent Pinchart wrote:
> >>> On Thu, Feb 28, 2019 at 09:04:03PM +0100, Jacopo Mondi wrote:
> >>>> Create video devices and subdevices associated with an ImgU unit, and
> >>>> link the entities in the media graph to prepare the device for capture
> >>>> operations at stream configuration time.
> >>>>
> >>>> As we support a single stream at the moment, always select imgu0.
> >>>>
> >>>> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> >>>> ---
> >>>>  src/libcamera/pipeline/ipu3/ipu3.cpp | 219 +++++++++++++++++++++++++--
> >>>>  1 file changed, 207 insertions(+), 12 deletions(-)
> >>>>
> >>>> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> >>>> index 4f1ab72debf8..9fa59c1bc97e 100644
> >>>> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> >>>> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> >>>> @@ -24,6 +24,28 @@ namespace libcamera {
> >>>>
> >>>>  LOG_DEFINE_CATEGORY(IPU3)
> >>>>
> >>>> +struct ImguDevice {
> >>>
> >>> s/ImguDevice/ImgUDevice/ ?
> >>
> >> I would like ImguDevice best, but I'll use ImgUDevice for consistency
> >> with the kernel documentation.
> >>
> >>>> +	ImguDevice()
> >>>> +		: imgu(nullptr), input(nullptr), output(nullptr),
> >>>> +		  viewfinder(nullptr), stat(nullptr) {}
> >>>
> >>> 	{
> >>> 	}
> >>>
> >>>> +
> >>>> +	~ImguDevice()
> >>>> +	{
> >>>> +		delete imgu;
> >>>> +		delete input;
> >>>> +		delete output;
> >>>> +		delete viewfinder;
> >>>> +		delete stat;
> >>>> +	}
> >>>
> >>> Unlike for the CIO2Device, this could be made a class, as I expect it
> >>> will have more code associated with it, and the ImgU instances will be
> >>> shared between the multiple camera instances. The linkImgu function
> >>> would be a good candidate.
> >>
> >> I'll move that to this class. I will has well add a "disableLinks()"
> >> methods as you suggested below.
> >>
> >>>> +
> >>>> +	V4L2Subdevice *imgu;
> >>>> +	V4L2Device *input;
> >>>> +	V4L2Device *output;
> >>>> +	V4L2Device *viewfinder;
> >>>> +	V4L2Device *stat;
> >>>> +	/* TODO: add param video device for 3A tuning */
> >>>
> >>> \todo for consistency, even if this isn't a doxygen comment ?
> >>
> >> Will change all of these.
> >>
> >>>> +};
> >>>> +
> >>>>  struct Cio2Device {
> >>>>  	Cio2Device()
> >>>>  		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
> >>>> @@ -44,6 +66,7 @@ class IPU3CameraData : public CameraData
> >>>>  {
> >>>>  public:
> >>>>  	Cio2Device cio2;
> >>>> +	ImguDevice *imgu;
> >>>>
> >>>>  	Stream stream_;
> >>>>  };
> >>>> @@ -71,6 +94,10 @@ public:
> >>>>  	bool match(DeviceEnumerator *enumerator);
> >>>>
> >>>>  private:
> >>>> +	static constexpr unsigned int IMGU_PAD_INPUT = 0;
> >>>> +	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
> >>>> +	static constexpr unsigned int IMGU_PAD_VF = 3;
> >>>> +	static constexpr unsigned int IMGU_PAD_STAT = 4;
> >>>>  	static constexpr unsigned int IPU3_BUF_NUM = 4;
> >>>
> >>> I'd move that to the ImgUDevice struct/class. You can then remove the
> >>> IMGU_ prefix.
> >>
> >> Agreed.
> >>
> >>>>
> >>>>  	IPU3CameraData *cameraData(const Camera *camera)
> >>>> @@ -79,9 +106,17 @@ private:
> >>>>  			PipelineHandler::cameraData(camera));
> >>>>  	}
> >>>>
> >>>> +	int linkImgu(ImguDevice *imgu);
> >>>> +
> >>>> +	V4L2Device *openDevice(MediaDevice *media, std::string &name);
> >>>> +	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
> >>>> +	int initImgu(ImguDevice *imgu);
> >>>>  	int initCio2(unsigned int index, Cio2Device *cio2);
> >>>>  	void registerCameras();
> >>>>
> >>>> +	ImguDevice imgu0_;
> >>>> +	ImguDevice imgu1_;
> >>>
> >>> Maybe an array with two entries ?
> >>
> >> They're two, and will stay two, but yes, I'll see how it looks.
> >>
> >>>> +
> >>>>  	std::shared_ptr<MediaDevice> cio2MediaDev_;
> >>>>  	std::shared_ptr<MediaDevice> imguMediaDev_;
> >>>>  };
> >>>> @@ -159,6 +194,15 @@ int PipelineHandlerIPU3::configureStreams(Camera *camera,
> >>>>  	V4L2DeviceFormat devFormat = {};
> >>>>  	int ret;
> >>>>
> >>>> +	/*
> >>>> +	 * TODO: dynamically assign ImgU devices; as of now, with a single
> >>>> +	 * stream supported, always use 'imgu0'.
> >>>
> >>> 	/**
> >>> 	 * \todo
> >>>
> >>> ?
> >>>
> >>>> +	 */
> >>>> +	data->imgu = &imgu0_;
> >>>
> >>> How about moving this to camera creation time, and assigned imgu0 to the
> >>> first sensor and imgu1 to the second one ?
> >>>
> >>
> >> I've put it here, as I assumed ImgU association with Cameras will
> >> depend on the number of required streams. If you confirm my
> >> understanding I would keep it here instead of moving it back and
> >> forth.
> >
> > Your understanding is correct, but with this implementation imgu0 is
> > used for both cameras, so we can only use one camera at a time. If we
> > assign imgu0 to the first camera and imgu1 to the second one we can
> > start using both cameras, with up to two streams each, and later expand
> > that to more streams.
> >
> 
> I see. I'll do the assignement there (and limit the number of
> supported cameras to two for now)
> 
> >>>> +	ret = linkImgu(data->imgu);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>>  	/*
> >>>>  	 * FIXME: as of now, the format gets applied to the sensor and is
> >>>>  	 * propagated along the pipeline. It should instead be applied on the
> >>>> @@ -334,17 +378,29 @@ bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
> >>>>  	if (cio2MediaDev_->open())
> >>>>  		goto error_release_mdev;
> >>>>
> >>>> +	if (imguMediaDev_->open())
> >>>> +		goto error_close_mdev;
> >>>> +
> >>>>  	if (cio2MediaDev_->disableLinks())
> >>>> -		goto error_close_cio2;
> >>>> +		goto error_close_mdev;
> >>>> +
> >>>> +	if (initImgu(&imgu0_))
> >>>> +		goto error_close_mdev;
> >>>> +
> >>>> +	if (initImgu(&imgu1_))
> >>>> +		goto error_close_mdev;
> >>>> +
> >>>>
> >>>>  	registerCameras();
> >>>>
> >>>>  	cio2MediaDev_->close();
> >>>> +	imguMediaDev_->close();
> >>>>
> >>>>  	return true;
> >>>>
> >>>> -error_close_cio2:
> >>>> +error_close_mdev:
> >>>>  	cio2MediaDev_->close();
> >>>> +	imguMediaDev_->close();
> >>>
> >>> Error handling will be simplified when you'll rebase. I'd go as far as
> >>> calling close() in the PipelineHandlerIPU3 destructor and remove the
> >>> error path here.
> >>
> >> It has been simplified by rebase, yes. I'll see how moving close() in
> >> destructor looks like (I think it's good)
> >>
> 
> Actually, the pipeline handler will be destroyed at library tear-down
> time, isn't it? So I don't think we can rely on its destructor to close the
> media devices...

It will be destroyed at the earlier of CameraManager::stop() time, when
the device is unplugged, or immediately after the match() function
returns an error. I think we'll need to forward the camera open() and
close() calls to the pipeline manager for normal operation. The error
path could still rely on the destructor, but that would be assymetrical,
so maybe not the best idea. Feel free to keep the calls here.

> >>>>
> >>>>  error_release_mdev:
> >>>>  	cio2MediaDev_->release();
> >>>> @@ -353,6 +409,153 @@ error_release_mdev:
> >>>>  	return false;
> >>>>  }
> >>>>
> >>>> +/* Link entities in the ImgU unit to prepare for capture operations. */
> >>>> +int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
> >>>> +{
> >>>> +	MediaLink *link;
> >>>> +	int ret;
> >>>> +
> >>>> +	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
> >>>> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> >>>> +	std::string inputName = imguName + " input";
> >>>> +	std::string outputName = imguName + " output";
> >>>> +	std::string viewfinderName = imguName + " viewfinder";
> >>>> +	std::string statName = imguName + " 3a stat";
> >>>> +
> >>>> +	ret = imguMediaDev_->open();
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = imguMediaDev_->disableLinks();
> >>>
> >>> This isn't a good idea, as you will interfere with the other ImgU. You
> >>> should enable/disable links selectively based on your needs.
> >>
> >> As I've said, I'll make a per-imgu instance link disabling method. I
> >> hope it is enough to disable all links in an imgu instance and we
> >> don't have to go link by link...
> >
> > I haven't checked recently, are the two ImgUs exposed as separate media
> > devices ? If so that's fine, otherwise you'll have to go link by link.
> 
> No, the imgu units are part of the same media device. But it's fine
> anyway, I will create a method that goes link by link :(

Just thinking out loud, would a helper in MediaDevice that would take a
vector of links be useful for other pipeline handlers ?

> >>>> +	if (ret) {
> >>>> +		imguMediaDev_->close();
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	/* Link entities to configure the IMGU unit for capture. */
> >>>> +	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
> >>>> +	if (!link) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get link '" << inputName << "':0 -> '"
> >>>> +			<< imguName << "':0";
> >>>
> >>> Ideally you should use the IMGU_PAD_* constants instead of hardcoding
> >>> them in error messages.
> >>
> >> Ah well, it's just more lines for a single printout, but ok.
> >>
> >>>> +		ret = -ENODEV;
> >>>> +		goto error_close_mediadev;
> >>>> +	}
> >>>> +	link->setEnabled(true);
> >>>> +
> >>>> +	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
> >>>> +	if (!link) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get link '" << imguName << "':2 -> '"
> >>>> +			<< outputName << "':0";
> >>>> +		ret = -ENODEV;
> >>>> +		goto error_close_mediadev;
> >>>> +	}
> >>>> +	link->setEnabled(true);
> >>>> +
> >>>> +	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
> >>>> +	if (!link) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get link '" << imguName << "':3 -> '"
> >>>> +			<< viewfinderName << "':0";
> >>>> +		ret = -ENODEV;
> >>>> +		goto error_close_mediadev;
> >>>> +	}
> >>>> +	link->setEnabled(true);
> >>>
> >>> We have a single stream, why do you enable both output and viewfinder ?
> >>
> >> From my testings, if I keep them disabled (== not linked) the system
> >> hangs even if I'm only capturing from the ouput video device. I will
> >> do some more testing, as setting up those nodes, queuing and dequeuing
> >> buffers there requires some not-nice code to keep track of the buffer
> >> indexes, as you've noticed.
> >
> > Another kernel bug fix candidate :-)
> 
> I plan to do more testing once I have included all these comments in
> v2. Will report how they go..
> 
> >>>> +
> >>>> +	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
> >>>> +	if (!link) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get link '" << imguName << "':4 -> '"
> >>>> +			<< statName << "':0";
> >>>> +		ret = -ENODEV;
> >>>> +		goto error_close_mediadev;
> >>>> +	}
> >>>> +	link->setEnabled(true);
> >>>
> >>> Same here, we don't make use of stats yes, there's no need to enable
> >>> this link.
> >>>
> >>>> +
> >>>> +	imguMediaDev_->close();
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +error_close_mediadev:
> >>>> +	imguMediaDev_->close();
> >>>
> >>> We really really need to think about how to handle open/close and write
> >>> down a set of rules. Please make sure this is captured in a \todo.
> >>>
> >>>> +
> >>>> +	return ret;
> >>>> +
> >>>> +}
> >>>> +
> >>>> +V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
> >>>> +					    std::string &name)
> >>>
> >>> const std::string &
> >>>
> >>> Same below.
> >>>
> >>>> +{
> >>>> +	V4L2Device *dev;
> >>>
> >>> You can move this line down to declare and initialize the variable at
> >>> the same time.
> >>>
> >>>> +
> >>>> +	MediaEntity *entity = media->getEntityByName(name);
> >>>> +	if (!entity) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get entity '" << name << "'";
> >>>> +		return nullptr;
> >>>> +	}
> >>>> +
> >>>> +	dev = new V4L2Device(entity);
> >>>> +	if (dev->open())
> >>>> +		return nullptr;
> >>>
> >>> You'll leak dev, a delete is needed.
> >>
> >> Ah, right! will fix.
> >>
> >>>> +
> >>>> +	return dev;
> >>>> +}
> >>>> +
> >>>> +V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
> >>>> +						  std::string &name)
> >>>> +{
> >>>> +	V4L2Subdevice *dev;
> >>>> +
> >>>> +	MediaEntity *entity = media->getEntityByName(name);
> >>>> +	if (!entity) {
> >>>> +		LOG(IPU3, Error)
> >>>> +			<< "Failed to get entity '" << name << "'";
> >>>> +		return nullptr;
> >>>> +	}
> >>>> +
> >>>> +	dev = new V4L2Subdevice(entity);
> >>>> +	if (dev->open())
> >>>
> >>> Leak.
> >>>
> >>>> +		return nullptr;
> >>>> +
> >>>> +	return dev;
> >>>> +}
> >>>
> >>> These two functions may be candidates for future generic helpers. Could
> >>> you document them and add a \todo to mention this ?
> >>
> >> Agreed!
> >>
> >>>> +
> >>>> +/* Create video devices and subdevices for the ImgU instance. */
> >>>> +int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
> >>>> +{
> >>>> +	unsigned int index = imgu == &imgu0_ ? 0 : 1;
> >>>
> >>> A bit ugly, how about adding an index field to the ImguDevice structure
> >>> ?
> >>
> >> Yes. But when would you intialize such a field? at match time? In this
> >> function? I'll see how it'll look
> >
> > I think I'd do it at match time.
> >
> >>>> +	std::string imguName = "ipu3-imgu " + std::to_string(index);
> >>>> +	std::string devName;
> >>>> +
> >>>> +	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
> >>>> +	if (!imgu->imgu)
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	devName = imguName + " input";
> >>>> +	imgu->input = openDevice(imguMediaDev_.get(), devName);
> >>>> +	if (!imgu->input)
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	devName = imguName + " output";
> >>>> +	imgu->output = openDevice(imguMediaDev_.get(), devName);
> >>>> +	if (!imgu->output)
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	devName = imguName + " viewfinder";
> >>>> +	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
> >>>> +	if (!imgu->viewfinder)
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	devName = imguName + " 3a stat";
> >>>> +	imgu->stat = openDevice(imguMediaDev_.get(), devName);
> >>>
> >>> You could drop devName and call
> >>>
> >>> 	imgu->state = openDevice(imguMediaDev_.get(), imguName + " 3a stat");
> >>>
> >>> Up to you.
> >>
> >> Less lines, so it's better
> >>
> >>>> +	if (!imgu->stat)
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>>  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >>>>  {
> >>>>  	int ret;
> >>>> @@ -400,16 +603,8 @@ int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
> >>>>  		return ret;
> >>>>
> >>>>  	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
> >>>> -	entity = cio2MediaDev_->getEntityByName(cio2Name);
> >>>> -	if (!entity) {
> >>>> -		LOG(IPU3, Error)
> >>>> -			<< "Failed to get entity '" << cio2Name << "'";
> >>>> -		return -EINVAL;
> >>>> -	}
> >>>> -
> >>>> -	cio2->output = new V4L2Device(entity);
> >>>> -	ret = cio2->output->open();
> >>>> -	if (ret)
> >>>> +	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
> >>>> +	if (!cio2->output)
> >>>>  		return ret;
> >>>>
> >>>>  	return 0;

Patch

diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 4f1ab72debf8..9fa59c1bc97e 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -24,6 +24,28 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(IPU3)
 
+struct ImguDevice {
+	ImguDevice()
+		: imgu(nullptr), input(nullptr), output(nullptr),
+		  viewfinder(nullptr), stat(nullptr) {}
+
+	~ImguDevice()
+	{
+		delete imgu;
+		delete input;
+		delete output;
+		delete viewfinder;
+		delete stat;
+	}
+
+	V4L2Subdevice *imgu;
+	V4L2Device *input;
+	V4L2Device *output;
+	V4L2Device *viewfinder;
+	V4L2Device *stat;
+	/* TODO: add param video device for 3A tuning */
+};
+
 struct Cio2Device {
 	Cio2Device()
 		: output(nullptr), csi2(nullptr), sensor(nullptr) {}
@@ -44,6 +66,7 @@  class IPU3CameraData : public CameraData
 {
 public:
 	Cio2Device cio2;
+	ImguDevice *imgu;
 
 	Stream stream_;
 };
@@ -71,6 +94,10 @@  public:
 	bool match(DeviceEnumerator *enumerator);
 
 private:
+	static constexpr unsigned int IMGU_PAD_INPUT = 0;
+	static constexpr unsigned int IMGU_PAD_OUTPUT = 2;
+	static constexpr unsigned int IMGU_PAD_VF = 3;
+	static constexpr unsigned int IMGU_PAD_STAT = 4;
 	static constexpr unsigned int IPU3_BUF_NUM = 4;
 
 	IPU3CameraData *cameraData(const Camera *camera)
@@ -79,9 +106,17 @@  private:
 			PipelineHandler::cameraData(camera));
 	}
 
+	int linkImgu(ImguDevice *imgu);
+
+	V4L2Device *openDevice(MediaDevice *media, std::string &name);
+	V4L2Subdevice *openSubdevice(MediaDevice *media, std::string &name);
+	int initImgu(ImguDevice *imgu);
 	int initCio2(unsigned int index, Cio2Device *cio2);
 	void registerCameras();
 
+	ImguDevice imgu0_;
+	ImguDevice imgu1_;
+
 	std::shared_ptr<MediaDevice> cio2MediaDev_;
 	std::shared_ptr<MediaDevice> imguMediaDev_;
 };
@@ -159,6 +194,15 @@  int PipelineHandlerIPU3::configureStreams(Camera *camera,
 	V4L2DeviceFormat devFormat = {};
 	int ret;
 
+	/*
+	 * TODO: dynamically assign ImgU devices; as of now, with a single
+	 * stream supported, always use 'imgu0'.
+	 */
+	data->imgu = &imgu0_;
+	ret = linkImgu(data->imgu);
+	if (ret)
+		return ret;
+
 	/*
 	 * FIXME: as of now, the format gets applied to the sensor and is
 	 * propagated along the pipeline. It should instead be applied on the
@@ -334,17 +378,29 @@  bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
 	if (cio2MediaDev_->open())
 		goto error_release_mdev;
 
+	if (imguMediaDev_->open())
+		goto error_close_mdev;
+
 	if (cio2MediaDev_->disableLinks())
-		goto error_close_cio2;
+		goto error_close_mdev;
+
+	if (initImgu(&imgu0_))
+		goto error_close_mdev;
+
+	if (initImgu(&imgu1_))
+		goto error_close_mdev;
+
 
 	registerCameras();
 
 	cio2MediaDev_->close();
+	imguMediaDev_->close();
 
 	return true;
 
-error_close_cio2:
+error_close_mdev:
 	cio2MediaDev_->close();
+	imguMediaDev_->close();
 
 error_release_mdev:
 	cio2MediaDev_->release();
@@ -353,6 +409,153 @@  error_release_mdev:
 	return false;
 }
 
+/* Link entities in the ImgU unit to prepare for capture operations. */
+int PipelineHandlerIPU3::linkImgu(ImguDevice *imguDevice)
+{
+	MediaLink *link;
+	int ret;
+
+	unsigned int index = imguDevice == &imgu0_ ? 0 : 1;
+	std::string imguName = "ipu3-imgu " + std::to_string(index);
+	std::string inputName = imguName + " input";
+	std::string outputName = imguName + " output";
+	std::string viewfinderName = imguName + " viewfinder";
+	std::string statName = imguName + " 3a stat";
+
+	ret = imguMediaDev_->open();
+	if (ret)
+		return ret;
+
+	ret = imguMediaDev_->disableLinks();
+	if (ret) {
+		imguMediaDev_->close();
+		return ret;
+	}
+
+	/* Link entities to configure the IMGU unit for capture. */
+	link = imguMediaDev_->link(inputName, 0, imguName, IMGU_PAD_INPUT);
+	if (!link) {
+		LOG(IPU3, Error)
+			<< "Failed to get link '" << inputName << "':0 -> '"
+			<< imguName << "':0";
+		ret = -ENODEV;
+		goto error_close_mediadev;
+	}
+	link->setEnabled(true);
+
+	link = imguMediaDev_->link(imguName, IMGU_PAD_OUTPUT, outputName, 0);
+	if (!link) {
+		LOG(IPU3, Error)
+			<< "Failed to get link '" << imguName << "':2 -> '"
+			<< outputName << "':0";
+		ret = -ENODEV;
+		goto error_close_mediadev;
+	}
+	link->setEnabled(true);
+
+	link = imguMediaDev_->link(imguName, IMGU_PAD_VF, viewfinderName, 0);
+	if (!link) {
+		LOG(IPU3, Error)
+			<< "Failed to get link '" << imguName << "':3 -> '"
+			<< viewfinderName << "':0";
+		ret = -ENODEV;
+		goto error_close_mediadev;
+	}
+	link->setEnabled(true);
+
+	link = imguMediaDev_->link(imguName, IMGU_PAD_STAT, statName, 0);
+	if (!link) {
+		LOG(IPU3, Error)
+			<< "Failed to get link '" << imguName << "':4 -> '"
+			<< statName << "':0";
+		ret = -ENODEV;
+		goto error_close_mediadev;
+	}
+	link->setEnabled(true);
+
+	imguMediaDev_->close();
+
+	return 0;
+
+error_close_mediadev:
+	imguMediaDev_->close();
+
+	return ret;
+
+}
+
+V4L2Device *PipelineHandlerIPU3::openDevice(MediaDevice *media,
+					    std::string &name)
+{
+	V4L2Device *dev;
+
+	MediaEntity *entity = media->getEntityByName(name);
+	if (!entity) {
+		LOG(IPU3, Error)
+			<< "Failed to get entity '" << name << "'";
+		return nullptr;
+	}
+
+	dev = new V4L2Device(entity);
+	if (dev->open())
+		return nullptr;
+
+	return dev;
+}
+
+V4L2Subdevice *PipelineHandlerIPU3::openSubdevice(MediaDevice *media,
+						  std::string &name)
+{
+	V4L2Subdevice *dev;
+
+	MediaEntity *entity = media->getEntityByName(name);
+	if (!entity) {
+		LOG(IPU3, Error)
+			<< "Failed to get entity '" << name << "'";
+		return nullptr;
+	}
+
+	dev = new V4L2Subdevice(entity);
+	if (dev->open())
+		return nullptr;
+
+	return dev;
+}
+
+/* Create video devices and subdevices for the ImgU instance. */
+int PipelineHandlerIPU3::initImgu(ImguDevice *imgu)
+{
+	unsigned int index = imgu == &imgu0_ ? 0 : 1;
+	std::string imguName = "ipu3-imgu " + std::to_string(index);
+	std::string devName;
+
+	imgu->imgu = openSubdevice(imguMediaDev_.get(), imguName);
+	if (!imgu->imgu)
+		return -ENODEV;
+
+	devName = imguName + " input";
+	imgu->input = openDevice(imguMediaDev_.get(), devName);
+	if (!imgu->input)
+		return -ENODEV;
+
+	devName = imguName + " output";
+	imgu->output = openDevice(imguMediaDev_.get(), devName);
+	if (!imgu->output)
+		return -ENODEV;
+
+	devName = imguName + " viewfinder";
+	imgu->viewfinder = openDevice(imguMediaDev_.get(), devName);
+	if (!imgu->viewfinder)
+		return -ENODEV;
+
+	devName = imguName + " 3a stat";
+	imgu->stat = openDevice(imguMediaDev_.get(), devName);
+	if (!imgu->stat)
+		return -ENODEV;
+
+	return 0;
+}
+
 int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
 {
 	int ret;
@@ -400,16 +603,8 @@  int PipelineHandlerIPU3::initCio2(unsigned int index, Cio2Device *cio2)
 		return ret;
 
 	std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
-	entity = cio2MediaDev_->getEntityByName(cio2Name);
-	if (!entity) {
-		LOG(IPU3, Error)
-			<< "Failed to get entity '" << cio2Name << "'";
-		return -EINVAL;
-	}
-
-	cio2->output = new V4L2Device(entity);
-	ret = cio2->output->open();
-	if (ret)
+	cio2->output = openDevice(cio2MediaDev_.get(), cio2Name);
+	if (!cio2->output)
 		return ret;
 
 	return 0;