[libcamera-devel,RFC,03/10] libcamera: mapped_framebuffer: Return plane begin address by MappedBuffer::maps()
diff mbox series

Message ID 20210816043138.957984-4-hiroh@chromium.org
State Superseded
Headers show
Series
  • Add offset to FrameBuffer::Plane
Related show

Commit Message

Hirokazu Honda Aug. 16, 2021, 4:31 a.m. UTC
MappedBuffer::maps() returns std::vector<MappedBuffer::Plane>.
Plane has the address, but the address points the beginning of the
buffer containing the plane.
This makes the Plane point the beginning of the plane. So
MappedBuffer::maps()[i].data() returns the address of i-th plane.

Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
---
 .../libcamera/internal/mapped_framebuffer.h   |  4 +-
 src/libcamera/mapped_framebuffer.cpp          | 51 +++++++++++++++----
 2 files changed, 44 insertions(+), 11 deletions(-)

Comments

Laurent Pinchart Aug. 17, 2021, 11:46 p.m. UTC | #1
Hi Hiro,

Thank you for the patch.

On Mon, Aug 16, 2021 at 01:31:31PM +0900, Hirokazu Honda wrote:
> MappedBuffer::maps() returns std::vector<MappedBuffer::Plane>.
> Plane has the address, but the address points the beginning of the
> buffer containing the plane.
> This makes the Plane point the beginning of the plane. So
> MappedBuffer::maps()[i].data() returns the address of i-th plane.
> 
> Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> ---
>  .../libcamera/internal/mapped_framebuffer.h   |  4 +-
>  src/libcamera/mapped_framebuffer.cpp          | 51 +++++++++++++++----
>  2 files changed, 44 insertions(+), 11 deletions(-)
> 
> diff --git a/include/libcamera/internal/mapped_framebuffer.h b/include/libcamera/internal/mapped_framebuffer.h
> index 3401a9fc..42479541 100644
> --- a/include/libcamera/internal/mapped_framebuffer.h
> +++ b/include/libcamera/internal/mapped_framebuffer.h
> @@ -30,12 +30,14 @@ public:
>  
>  	bool isValid() const { return error_ == 0; }
>  	int error() const { return error_; }
> -	const std::vector<Plane> &maps() const { return maps_; }
> +	/* \todo rename to planes(). */

I'm fine with a todo comment in this patch, but could you do the rename
as part of the series, maybe as a last patch on top ?

> +	const std::vector<Plane> &maps() const { return planes_; }
>  
>  protected:
>  	MappedBuffer();
>  
>  	int error_;
> +	std::vector<Plane> planes_;
>  	std::vector<Plane> maps_;
>  
>  private:
> diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
> index 2ebe9fdb..b0ba89b0 100644
> --- a/src/libcamera/mapped_framebuffer.cpp
> +++ b/src/libcamera/mapped_framebuffer.cpp
> @@ -9,6 +9,7 @@
>  
>  #include <errno.h>
>  #include <sys/mman.h>
> +#include <unistd.h>
>  
>  #include <libcamera/base/log.h>
>  
> @@ -79,6 +80,7 @@ MappedBuffer::MappedBuffer(MappedBuffer &&other)
>  MappedBuffer &MappedBuffer::operator=(MappedBuffer &&other)
>  {
>  	error_ = other.error_;
> +	planes_ = std::move(other.planes_);
>  	maps_ = std::move(other.maps_);
>  	other.error_ = -ENOENT;
>  
> @@ -127,10 +129,18 @@ MappedBuffer::~MappedBuffer()
>   */
>  
>  /**
> - * \var MappedBuffer::maps_
> + * \var MappedBuffer::planes_
>   * \brief Stores the internal mapped planes
>   *
>   * MappedBuffer derived classes shall store the mappings they create in this
> + * vector which points the beginning of mapped plane addresses.
> + */
> +
> +/**
> + * \var MappedBuffer::maps_
> + * \brief Stores the mapped buffer
> + *
> + * MappedBuffer derived classes shall store the mappings they create in this
>   * vector which is parsed during destruct to unmap any memory mappings which
>   * completed successfully.
>   */
> @@ -167,7 +177,8 @@ MappedBuffer::~MappedBuffer()
>   */
>  MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
>  {
> -	maps_.reserve(buffer->planes().size());
> +	ASSERT(!buffer->planes().empty());
> +	planes_.reserve(buffer->planes().size());
>  
>  	int mmapFlags = 0;
>  
> @@ -177,18 +188,38 @@ MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
>  	if (flags & MapFlag::Write)
>  		mmapFlags |= PROT_WRITE;
>  
> +	int prevFd = -1;
>  	for (const FrameBuffer::Plane &plane : buffer->planes()) {
> -		void *address = mmap(nullptr, plane.length, mmapFlags,
> -				     MAP_SHARED, plane.fd.fd(), 0);
> -		if (address == MAP_FAILED) {
> -			error_ = -errno;
> -			LOG(Buffer, Error) << "Failed to mmap plane: "
> -					   << strerror(-error_);
> -			break;
> +		const int fd = plane.fd.fd();
> +		if (prevFd != fd) {

Could there be a case where plane 1 and 3 share the same dmabuf but
plane 2 uses a different once ? It may be a bit far-fetched, but how
about turning the maps_ vector into a std::map<int, Plane> ? That way
we'll correctly support this case, with no overhead and an
implementation that shouldn't be more complicated.

> +			const size_t length = lseek(fd, 0, SEEK_END);
> +			void *address = mmap(nullptr, length, mmapFlags,
> +					     MAP_SHARED, fd, 0);

Hmmm... I could imagine that in some case the dmabuf would be larger
than the sum of the planes, and it may then be possible to only map part
of the dmabuf. Let's consider this for a possible future enhancement, as
virtual address space should hopefully not be a limiting factor in most
case. Maybe a todo comment would be appropriate ?

			/**
			 * \todo Should we try to only map the portions of the
			 * dmabuf that are used by planes ?
			 */

> +			if (address == MAP_FAILED) {
> +				error_ = -errno;
> +				LOG(Buffer, Error) << "Failed to mmap plane: "
> +						   << strerror(-error_);
> +				return;
> +			}
> +			maps_.emplace_back(static_cast<uint8_t *>(address),
> +					   length);
> +			prevFd = fd;
>  		}
>  
> -		maps_.emplace_back(static_cast<uint8_t *>(address), plane.length);
> +		const size_t length = maps_.back().size();
> +		if (plane.offset + plane.length > length) {

Should we protect ourselves against arithmetic overflows here ?

		if (plane.offset > length ||
		    plane.offset + plane.length > length) {

> +			LOG(Buffer, Fatal) << "plane is out of buffer: "
> +					   << "buffer length=" << length
> +					   << ", plane offset=" << plane.offset
> +					   << ", plane length=" << plane.length;
> +			return;
> +		}
> +
> +		uint8_t *buf = maps_.back().data();
> +		planes_.emplace_back(buf + plane.offset, plane.length);
>  	}
> +
> +	ASSERT(maps_.size() == 1u);

I'm not sure to understand this. Won't a multi-planar frame buffer with
different dmabufs fail this assertion ?

>  }
>  
>  } /* namespace libcamera */
Hirokazu Honda Aug. 20, 2021, 8:19 a.m. UTC | #2
Hi Laurent, thank you for the comment.

On Wed, Aug 18, 2021 at 8:46 AM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Hiro,
>
> Thank you for the patch.
>
> On Mon, Aug 16, 2021 at 01:31:31PM +0900, Hirokazu Honda wrote:
> > MappedBuffer::maps() returns std::vector<MappedBuffer::Plane>.
> > Plane has the address, but the address points the beginning of the
> > buffer containing the plane.
> > This makes the Plane point the beginning of the plane. So
> > MappedBuffer::maps()[i].data() returns the address of i-th plane.
> >
> > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > ---
> >  .../libcamera/internal/mapped_framebuffer.h   |  4 +-
> >  src/libcamera/mapped_framebuffer.cpp          | 51 +++++++++++++++----
> >  2 files changed, 44 insertions(+), 11 deletions(-)
> >
> > diff --git a/include/libcamera/internal/mapped_framebuffer.h b/include/libcamera/internal/mapped_framebuffer.h
> > index 3401a9fc..42479541 100644
> > --- a/include/libcamera/internal/mapped_framebuffer.h
> > +++ b/include/libcamera/internal/mapped_framebuffer.h
> > @@ -30,12 +30,14 @@ public:
> >
> >       bool isValid() const { return error_ == 0; }
> >       int error() const { return error_; }
> > -     const std::vector<Plane> &maps() const { return maps_; }
> > +     /* \todo rename to planes(). */
>
> I'm fine with a todo comment in this patch, but could you do the rename
> as part of the series, maybe as a last patch on top ?
>
> > +     const std::vector<Plane> &maps() const { return planes_; }
> >
> >  protected:
> >       MappedBuffer();
> >
> >       int error_;
> > +     std::vector<Plane> planes_;
> >       std::vector<Plane> maps_;
> >
> >  private:
> > diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
> > index 2ebe9fdb..b0ba89b0 100644
> > --- a/src/libcamera/mapped_framebuffer.cpp
> > +++ b/src/libcamera/mapped_framebuffer.cpp
> > @@ -9,6 +9,7 @@
> >
> >  #include <errno.h>
> >  #include <sys/mman.h>
> > +#include <unistd.h>
> >
> >  #include <libcamera/base/log.h>
> >
> > @@ -79,6 +80,7 @@ MappedBuffer::MappedBuffer(MappedBuffer &&other)
> >  MappedBuffer &MappedBuffer::operator=(MappedBuffer &&other)
> >  {
> >       error_ = other.error_;
> > +     planes_ = std::move(other.planes_);
> >       maps_ = std::move(other.maps_);
> >       other.error_ = -ENOENT;
> >
> > @@ -127,10 +129,18 @@ MappedBuffer::~MappedBuffer()
> >   */
> >
> >  /**
> > - * \var MappedBuffer::maps_
> > + * \var MappedBuffer::planes_
> >   * \brief Stores the internal mapped planes
> >   *
> >   * MappedBuffer derived classes shall store the mappings they create in this
> > + * vector which points the beginning of mapped plane addresses.
> > + */
> > +
> > +/**
> > + * \var MappedBuffer::maps_
> > + * \brief Stores the mapped buffer
> > + *
> > + * MappedBuffer derived classes shall store the mappings they create in this
> >   * vector which is parsed during destruct to unmap any memory mappings which
> >   * completed successfully.
> >   */
> > @@ -167,7 +177,8 @@ MappedBuffer::~MappedBuffer()
> >   */
> >  MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
> >  {
> > -     maps_.reserve(buffer->planes().size());
> > +     ASSERT(!buffer->planes().empty());
> > +     planes_.reserve(buffer->planes().size());
> >
> >       int mmapFlags = 0;
> >
> > @@ -177,18 +188,38 @@ MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
> >       if (flags & MapFlag::Write)
> >               mmapFlags |= PROT_WRITE;
> >
> > +     int prevFd = -1;
> >       for (const FrameBuffer::Plane &plane : buffer->planes()) {
> > -             void *address = mmap(nullptr, plane.length, mmapFlags,
> > -                                  MAP_SHARED, plane.fd.fd(), 0);
> > -             if (address == MAP_FAILED) {
> > -                     error_ = -errno;
> > -                     LOG(Buffer, Error) << "Failed to mmap plane: "
> > -                                        << strerror(-error_);
> > -                     break;
> > +             const int fd = plane.fd.fd();
> > +             if (prevFd != fd) {
>
> Could there be a case where plane 1 and 3 share the same dmabuf but
> plane 2 uses a different once ? It may be a bit far-fetched, but how
> about turning the maps_ vector into a std::map<int, Plane> ? That way
> we'll correctly support this case, with no overhead and an
> implementation that shouldn't be more complicated.
>
> > +                     const size_t length = lseek(fd, 0, SEEK_END);
> > +                     void *address = mmap(nullptr, length, mmapFlags,
> > +                                          MAP_SHARED, fd, 0);
>
> Hmmm... I could imagine that in some case the dmabuf would be larger
> than the sum of the planes, and it may then be possible to only map part
> of the dmabuf. Let's consider this for a possible future enhancement, as
> virtual address space should hopefully not be a limiting factor in most
> case. Maybe a todo comment would be appropriate ?
>
>                         /**
>                          * \todo Should we try to only map the portions of the
>                          * dmabuf that are used by planes ?
>                          */
>
> > +                     if (address == MAP_FAILED) {
> > +                             error_ = -errno;
> > +                             LOG(Buffer, Error) << "Failed to mmap plane: "
> > +                                                << strerror(-error_);
> > +                             return;
> > +                     }
> > +                     maps_.emplace_back(static_cast<uint8_t *>(address),
> > +                                        length);
> > +                     prevFd = fd;
> >               }
> >
> > -             maps_.emplace_back(static_cast<uint8_t *>(address), plane.length);
> > +             const size_t length = maps_.back().size();
> > +             if (plane.offset + plane.length > length) {
>
> Should we protect ourselves against arithmetic overflows here ?
>
>                 if (plane.offset > length ||
>                     plane.offset + plane.length > length) {
>

Thanks. By the way, ideally we should introduce something like
base/numerics in chromium.
https://source.chromium.org/chromium/chromium/src/+/main:base/numerics/README.md

So I can do like
size_t planeEnd;
if (!CheckAdd(plane.offset, plane.length).AssignIfValid(planeEnd)) {
 // Overflow!
}
Or more aggressively if (base::checked_cast<size_t>(plane.offset,
plane.length) > length), which causes process crash if overflow
happens.

> > +                     LOG(Buffer, Fatal) << "plane is out of buffer: "
> > +                                        << "buffer length=" << length
> > +                                        << ", plane offset=" << plane.offset
> > +                                        << ", plane length=" << plane.length;
> > +                     return;
> > +             }
> > +
> > +             uint8_t *buf = maps_.back().data();
> > +             planes_.emplace_back(buf + plane.offset, plane.length);
> >       }
> > +
> > +     ASSERT(maps_.size() == 1u);
>
> I'm not sure to understand this. Won't a multi-planar frame buffer with
> different dmabufs fail this assertion ?
>

Right, this is brought from your comment to my previous solution.
We should remove now.

Best Regards,
-Hiro

> >  }
> >
> >  } /* namespace libcamera */
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart Aug. 20, 2021, 12:10 p.m. UTC | #3
Hi Hiro,

On Fri, Aug 20, 2021 at 05:19:59PM +0900, Hirokazu Honda wrote:
> On Wed, Aug 18, 2021 at 8:46 AM Laurent Pinchart wrote:
> > On Mon, Aug 16, 2021 at 01:31:31PM +0900, Hirokazu Honda wrote:
> > > MappedBuffer::maps() returns std::vector<MappedBuffer::Plane>.
> > > Plane has the address, but the address points the beginning of the
> > > buffer containing the plane.
> > > This makes the Plane point the beginning of the plane. So
> > > MappedBuffer::maps()[i].data() returns the address of i-th plane.
> > >
> > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > ---
> > >  .../libcamera/internal/mapped_framebuffer.h   |  4 +-
> > >  src/libcamera/mapped_framebuffer.cpp          | 51 +++++++++++++++----
> > >  2 files changed, 44 insertions(+), 11 deletions(-)
> > >
> > > diff --git a/include/libcamera/internal/mapped_framebuffer.h b/include/libcamera/internal/mapped_framebuffer.h
> > > index 3401a9fc..42479541 100644
> > > --- a/include/libcamera/internal/mapped_framebuffer.h
> > > +++ b/include/libcamera/internal/mapped_framebuffer.h
> > > @@ -30,12 +30,14 @@ public:
> > >
> > >       bool isValid() const { return error_ == 0; }
> > >       int error() const { return error_; }
> > > -     const std::vector<Plane> &maps() const { return maps_; }
> > > +     /* \todo rename to planes(). */
> >
> > I'm fine with a todo comment in this patch, but could you do the rename
> > as part of the series, maybe as a last patch on top ?
> >
> > > +     const std::vector<Plane> &maps() const { return planes_; }
> > >
> > >  protected:
> > >       MappedBuffer();
> > >
> > >       int error_;
> > > +     std::vector<Plane> planes_;
> > >       std::vector<Plane> maps_;
> > >
> > >  private:
> > > diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
> > > index 2ebe9fdb..b0ba89b0 100644
> > > --- a/src/libcamera/mapped_framebuffer.cpp
> > > +++ b/src/libcamera/mapped_framebuffer.cpp
> > > @@ -9,6 +9,7 @@
> > >
> > >  #include <errno.h>
> > >  #include <sys/mman.h>
> > > +#include <unistd.h>
> > >
> > >  #include <libcamera/base/log.h>
> > >
> > > @@ -79,6 +80,7 @@ MappedBuffer::MappedBuffer(MappedBuffer &&other)
> > >  MappedBuffer &MappedBuffer::operator=(MappedBuffer &&other)
> > >  {
> > >       error_ = other.error_;
> > > +     planes_ = std::move(other.planes_);
> > >       maps_ = std::move(other.maps_);
> > >       other.error_ = -ENOENT;
> > >
> > > @@ -127,10 +129,18 @@ MappedBuffer::~MappedBuffer()
> > >   */
> > >
> > >  /**
> > > - * \var MappedBuffer::maps_
> > > + * \var MappedBuffer::planes_
> > >   * \brief Stores the internal mapped planes
> > >   *
> > >   * MappedBuffer derived classes shall store the mappings they create in this
> > > + * vector which points the beginning of mapped plane addresses.
> > > + */
> > > +
> > > +/**
> > > + * \var MappedBuffer::maps_
> > > + * \brief Stores the mapped buffer
> > > + *
> > > + * MappedBuffer derived classes shall store the mappings they create in this
> > >   * vector which is parsed during destruct to unmap any memory mappings which
> > >   * completed successfully.
> > >   */
> > > @@ -167,7 +177,8 @@ MappedBuffer::~MappedBuffer()
> > >   */
> > >  MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
> > >  {
> > > -     maps_.reserve(buffer->planes().size());
> > > +     ASSERT(!buffer->planes().empty());
> > > +     planes_.reserve(buffer->planes().size());
> > >
> > >       int mmapFlags = 0;
> > >
> > > @@ -177,18 +188,38 @@ MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
> > >       if (flags & MapFlag::Write)
> > >               mmapFlags |= PROT_WRITE;
> > >
> > > +     int prevFd = -1;
> > >       for (const FrameBuffer::Plane &plane : buffer->planes()) {
> > > -             void *address = mmap(nullptr, plane.length, mmapFlags,
> > > -                                  MAP_SHARED, plane.fd.fd(), 0);
> > > -             if (address == MAP_FAILED) {
> > > -                     error_ = -errno;
> > > -                     LOG(Buffer, Error) << "Failed to mmap plane: "
> > > -                                        << strerror(-error_);
> > > -                     break;
> > > +             const int fd = plane.fd.fd();
> > > +             if (prevFd != fd) {
> >
> > Could there be a case where plane 1 and 3 share the same dmabuf but
> > plane 2 uses a different once ? It may be a bit far-fetched, but how
> > about turning the maps_ vector into a std::map<int, Plane> ? That way
> > we'll correctly support this case, with no overhead and an
> > implementation that shouldn't be more complicated.
> >
> > > +                     const size_t length = lseek(fd, 0, SEEK_END);
> > > +                     void *address = mmap(nullptr, length, mmapFlags,
> > > +                                          MAP_SHARED, fd, 0);
> >
> > Hmmm... I could imagine that in some case the dmabuf would be larger
> > than the sum of the planes, and it may then be possible to only map part
> > of the dmabuf. Let's consider this for a possible future enhancement, as
> > virtual address space should hopefully not be a limiting factor in most
> > case. Maybe a todo comment would be appropriate ?
> >
> >                         /**
> >                          * \todo Should we try to only map the portions of the
> >                          * dmabuf that are used by planes ?
> >                          */
> >
> > > +                     if (address == MAP_FAILED) {
> > > +                             error_ = -errno;
> > > +                             LOG(Buffer, Error) << "Failed to mmap plane: "
> > > +                                                << strerror(-error_);
> > > +                             return;
> > > +                     }
> > > +                     maps_.emplace_back(static_cast<uint8_t *>(address),
> > > +                                        length);
> > > +                     prevFd = fd;
> > >               }
> > >
> > > -             maps_.emplace_back(static_cast<uint8_t *>(address), plane.length);
> > > +             const size_t length = maps_.back().size();
> > > +             if (plane.offset + plane.length > length) {
> >
> > Should we protect ourselves against arithmetic overflows here ?
> >
> >                 if (plane.offset > length ||
> >                     plane.offset + plane.length > length) {
> >
> 
> Thanks. By the way, ideally we should introduce something like
> base/numerics in chromium.
> https://source.chromium.org/chromium/chromium/src/+/main:base/numerics/README.md

I was actually thinking about adding helpers to utils.h for this when
reviewing your patch :-) I think we'll end up doing so at some point.

> So I can do like
> size_t planeEnd;
> if (!CheckAdd(plane.offset, plane.length).AssignIfValid(planeEnd)) {
>  // Overflow!
> }
> Or more aggressively if (base::checked_cast<size_t>(plane.offset,
> plane.length) > length), which causes process crash if overflow
> happens.
> 
> > > +                     LOG(Buffer, Fatal) << "plane is out of buffer: "
> > > +                                        << "buffer length=" << length
> > > +                                        << ", plane offset=" << plane.offset
> > > +                                        << ", plane length=" << plane.length;
> > > +                     return;
> > > +             }
> > > +
> > > +             uint8_t *buf = maps_.back().data();
> > > +             planes_.emplace_back(buf + plane.offset, plane.length);
> > >       }
> > > +
> > > +     ASSERT(maps_.size() == 1u);
> >
> > I'm not sure to understand this. Won't a multi-planar frame buffer with
> > different dmabufs fail this assertion ?
> 
> Right, this is brought from your comment to my previous solution.
> We should remove now.
> 
> > >  }
> > >
> > >  } /* namespace libcamera */

Patch
diff mbox series

diff --git a/include/libcamera/internal/mapped_framebuffer.h b/include/libcamera/internal/mapped_framebuffer.h
index 3401a9fc..42479541 100644
--- a/include/libcamera/internal/mapped_framebuffer.h
+++ b/include/libcamera/internal/mapped_framebuffer.h
@@ -30,12 +30,14 @@  public:
 
 	bool isValid() const { return error_ == 0; }
 	int error() const { return error_; }
-	const std::vector<Plane> &maps() const { return maps_; }
+	/* \todo rename to planes(). */
+	const std::vector<Plane> &maps() const { return planes_; }
 
 protected:
 	MappedBuffer();
 
 	int error_;
+	std::vector<Plane> planes_;
 	std::vector<Plane> maps_;
 
 private:
diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
index 2ebe9fdb..b0ba89b0 100644
--- a/src/libcamera/mapped_framebuffer.cpp
+++ b/src/libcamera/mapped_framebuffer.cpp
@@ -9,6 +9,7 @@ 
 
 #include <errno.h>
 #include <sys/mman.h>
+#include <unistd.h>
 
 #include <libcamera/base/log.h>
 
@@ -79,6 +80,7 @@  MappedBuffer::MappedBuffer(MappedBuffer &&other)
 MappedBuffer &MappedBuffer::operator=(MappedBuffer &&other)
 {
 	error_ = other.error_;
+	planes_ = std::move(other.planes_);
 	maps_ = std::move(other.maps_);
 	other.error_ = -ENOENT;
 
@@ -127,10 +129,18 @@  MappedBuffer::~MappedBuffer()
  */
 
 /**
- * \var MappedBuffer::maps_
+ * \var MappedBuffer::planes_
  * \brief Stores the internal mapped planes
  *
  * MappedBuffer derived classes shall store the mappings they create in this
+ * vector which points the beginning of mapped plane addresses.
+ */
+
+/**
+ * \var MappedBuffer::maps_
+ * \brief Stores the mapped buffer
+ *
+ * MappedBuffer derived classes shall store the mappings they create in this
  * vector which is parsed during destruct to unmap any memory mappings which
  * completed successfully.
  */
@@ -167,7 +177,8 @@  MappedBuffer::~MappedBuffer()
  */
 MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
 {
-	maps_.reserve(buffer->planes().size());
+	ASSERT(!buffer->planes().empty());
+	planes_.reserve(buffer->planes().size());
 
 	int mmapFlags = 0;
 
@@ -177,18 +188,38 @@  MappedFrameBuffer::MappedFrameBuffer(const FrameBuffer *buffer, MapFlags flags)
 	if (flags & MapFlag::Write)
 		mmapFlags |= PROT_WRITE;
 
+	int prevFd = -1;
 	for (const FrameBuffer::Plane &plane : buffer->planes()) {
-		void *address = mmap(nullptr, plane.length, mmapFlags,
-				     MAP_SHARED, plane.fd.fd(), 0);
-		if (address == MAP_FAILED) {
-			error_ = -errno;
-			LOG(Buffer, Error) << "Failed to mmap plane: "
-					   << strerror(-error_);
-			break;
+		const int fd = plane.fd.fd();
+		if (prevFd != fd) {
+			const size_t length = lseek(fd, 0, SEEK_END);
+			void *address = mmap(nullptr, length, mmapFlags,
+					     MAP_SHARED, fd, 0);
+			if (address == MAP_FAILED) {
+				error_ = -errno;
+				LOG(Buffer, Error) << "Failed to mmap plane: "
+						   << strerror(-error_);
+				return;
+			}
+			maps_.emplace_back(static_cast<uint8_t *>(address),
+					   length);
+			prevFd = fd;
 		}
 
-		maps_.emplace_back(static_cast<uint8_t *>(address), plane.length);
+		const size_t length = maps_.back().size();
+		if (plane.offset + plane.length > length) {
+			LOG(Buffer, Fatal) << "plane is out of buffer: "
+					   << "buffer length=" << length
+					   << ", plane offset=" << plane.offset
+					   << ", plane length=" << plane.length;
+			return;
+		}
+
+		uint8_t *buf = maps_.back().data();
+		planes_.emplace_back(buf + plane.offset, plane.length);
 	}
+
+	ASSERT(maps_.size() == 1u);
 }
 
 } /* namespace libcamera */