v4l2: v4l2_camera_proxy: Handle 64-bit time_t buffer ioctls
diff mbox series

Message ID 20261016061602.1710285-1-ragaprasanth@gmail.com
State Superseded
Headers show
Series
  • v4l2: v4l2_camera_proxy: Handle 64-bit time_t buffer ioctls
Related show

Commit Message

Prasanth Ramesh Oct. 16, 2026, 6:16 a.m. UTC
On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
time_t, but applications are now usually built with _TIME_BITS=64.
struct v4l2_buffer embeds a struct timeval, so for them it grows from
68 to 80 bytes on 32-bit Arm, and VIDIOC_QUERYBUF, VIDIOC_QBUF,
VIDIOC_DQBUF and VIDIOC_PREPARE_BUF get different request numbers.
These fail with ENOTTY and streaming does not work.

Recognise the 64-bit time_t requests and convert the buffer to and from
the native layout around the existing handlers. Only the timestamp
differs between the two layouts. Buffer timestamps are monotonic, so
they fit in the native timeval. Nothing changes where both layouts are
the same.

Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/290
Signed-off-by: Prasanth R <prasanth.r@lineo.co.jp>
---
This applies on top of Paul's "v4l2: v4l2_compat: Handle __ioctl_time64"
(https://patchwork.libcamera.org/patch/25925/), which is needed for the
ioctls to reach the proxy at all.

Tested on a Raspberry Pi 4 with a 32-bit, 64-bit time_t userspace (Yocto)
and a UVC camera, libcamera v0.4.0 with both patches:
libcamerify v4l2-ctl --stream-mmap --stream-count=30 streams at 15 fps,
5/5 runs; without this patch VIDIOC_QUERYBUF fails with ENOTTY.

 src/v4l2/v4l2_camera_proxy.cpp | 76 ++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

Comments

Laurent Pinchart Oct. 22, 2026, 7:09 p.m. UTC | #1
Hi Prasanth,

On Fri, Oct 16, 2026 at 03:16:02PM +0900, Prasanth R wrote:
> On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
> time_t, but applications are now usually built with _TIME_BITS=64.
> struct v4l2_buffer embeds a struct timeval, so for them it grows from
> 68 to 80 bytes on 32-bit Arm, and VIDIOC_QUERYBUF, VIDIOC_QBUF,
> VIDIOC_DQBUF and VIDIOC_PREPARE_BUF get different request numbers.
> These fail with ENOTTY and streaming does not work.
> 
> Recognise the 64-bit time_t requests and convert the buffer to and from
> the native layout around the existing handlers. Only the timestamp
> differs between the two layouts. Buffer timestamps are monotonic, so
> they fit in the native timeval. Nothing changes where both layouts are
> the same.
> 
> Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/290
> Signed-off-by: Prasanth R <prasanth.r@lineo.co.jp>
> ---
> This applies on top of Paul's "v4l2: v4l2_compat: Handle __ioctl_time64"
> (https://patchwork.libcamera.org/patch/25925/), which is needed for the
> ioctls to reach the proxy at all.
> 
> Tested on a Raspberry Pi 4 with a 32-bit, 64-bit time_t userspace (Yocto)
> and a UVC camera, libcamera v0.4.0 with both patches:
> libcamerify v4l2-ctl --stream-mmap --stream-count=30 streams at 15 fps,
> 5/5 runs; without this patch VIDIOC_QUERYBUF fails with ENOTTY.

Our of curiosity, why are you running a 32-bit userspace with 64-bit
time_t on a RPi 4 ?

>  src/v4l2/v4l2_camera_proxy.cpp | 76 ++++++++++++++++++++++++++++++++++
>  1 file changed, 76 insertions(+)
> 
> diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
> index 559ffc617..554e81f8d 100644
> --- a/src/v4l2/v4l2_camera_proxy.cpp
> +++ b/src/v4l2/v4l2_camera_proxy.cpp
> @@ -11,6 +11,7 @@
>  #include <errno.h>
>  #include <numeric>
>  #include <set>
> +#include <stddef.h>
>  #include <string.h>
>  #include <sys/mman.h>
>  #include <unistd.h>
> @@ -39,6 +40,64 @@ using namespace std::literals::chrono_literals;
>  
>  LOG_DECLARE_CATEGORY(V4L2Compat)
>  
> +namespace {
> +
> +/*
> + * On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
> + * time_t (see src/v4l2/meson.build), while applications are commonly built
> + * with _TIME_BITS=64. struct v4l2_buffer embeds a struct timeval, so the two
> + * disagree on the structure size, and thus on the buffer ioctl numbers. Only
> + * the timestamp differs, the fields before and after it are the same.
> + */
> +struct v4l2_buffer_time64 {
> +	__u8 head[offsetof(struct v4l2_buffer, timestamp)];
> +	struct {
> +		long long tv_sec;
> +		long long tv_usec;
> +	} timestamp;
> +	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
> +};
> +
> +/*
> + * Return the native request for a 64-bit time_t buffer ioctl, or 0 if the
> + * request is not one or both layouts are the same.
> + */
> +unsigned int bufferRequestFromTime64(unsigned int request)
> +{
> +	if (sizeof(struct v4l2_buffer_time64) == sizeof(struct v4l2_buffer))
> +		return 0;
> +
> +	switch (request) {
> +	case _IOWR('V', 9, struct v4l2_buffer_time64):	/* VIDIOC_QUERYBUF */
> +	case _IOWR('V', 15, struct v4l2_buffer_time64):	/* VIDIOC_QBUF */
> +	case _IOWR('V', 17, struct v4l2_buffer_time64):	/* VIDIOC_DQBUF */
> +	case _IOWR('V', 93, struct v4l2_buffer_time64):	/* VIDIOC_PREPARE_BUF */
> +		return _IOWR('V', _IOC_NR(request), struct v4l2_buffer);
> +	default:
> +		return 0;
> +	}
> +}
> +
> +void bufferFromTime64(const struct v4l2_buffer_time64 &src,
> +		      struct v4l2_buffer *dst)
> +{
> +	memcpy(dst, src.head, sizeof(src.head));
> +	dst->timestamp.tv_sec = src.timestamp.tv_sec;
> +	dst->timestamp.tv_usec = src.timestamp.tv_usec;
> +	memcpy(&dst->timecode, src.tail, sizeof(src.tail));
> +}
> +
> +void bufferToTime64(const struct v4l2_buffer &src,
> +		    struct v4l2_buffer_time64 *dst)
> +{
> +	memcpy(dst->head, &src, sizeof(dst->head));
> +	dst->timestamp.tv_sec = src.timestamp.tv_sec;
> +	dst->timestamp.tv_usec = src.timestamp.tv_usec;
> +	memcpy(dst->tail, &src.timecode, sizeof(dst->tail));
> +}
> +
> +} /* namespace */
> +
>  V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
>  				 std::shared_ptr<Camera> camera)
>  	: refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
> @@ -873,6 +932,20 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
>  		return -1;
>  	}
>  
> +	/*
> +	 * Translate buffer ioctls from applications built with a 64-bit
> +	 * time_t to the native struct v4l2_buffer layout.
> +	 */
> +	struct v4l2_buffer_time64 *buf64 = nullptr;
> +	struct v4l2_buffer buf = {};
> +	unsigned int bufRequest = bufferRequestFromTime64(request);
> +	if (bufRequest) {
> +		buf64 = static_cast<struct v4l2_buffer_time64 *>(arg);
> +		bufferFromTime64(*buf64, &buf);
> +		request = bufRequest;
> +		arg = &buf;
> +	}
> +
>  	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
>  		errno = ENOTTY;
>  		return -1;
> @@ -955,6 +1028,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
>  		return -1;
>  	}
>  
> +	if (buf64)
> +		bufferToTime64(buf, buf64);
> +
>  	return ret;
>  }
>
Prasanth Ramesh Oct. 23, 2026, 4:25 a.m. UTC | #2
Hi Laurent,

I was looking at Y38k issues on 32-bit Arm. OpenEmbedded now builds 32-bit
Arm userspace with 64-bit time_t by default, and meta-openembedded had a
workaround that turned it off for libcamera. While removing that workaround
(merged last week), I found this gap. The Pi 4 is the only board with a
camera I have on hand to test, the real target is a 32-bit Arm customer
device.

Thanks,
Prasanth R


On Fri, Oct 23, 2026 at 4:09 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi Prasanth,
>
> On Fri, Oct 16, 2026 at 03:16:02PM +0900, Prasanth R wrote:
> > On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
> > time_t, but applications are now usually built with _TIME_BITS=64.
> > struct v4l2_buffer embeds a struct timeval, so for them it grows from
> > 68 to 80 bytes on 32-bit Arm, and VIDIOC_QUERYBUF, VIDIOC_QBUF,
> > VIDIOC_DQBUF and VIDIOC_PREPARE_BUF get different request numbers.
> > These fail with ENOTTY and streaming does not work.
> >
> > Recognise the 64-bit time_t requests and convert the buffer to and from
> > the native layout around the existing handlers. Only the timestamp
> > differs between the two layouts. Buffer timestamps are monotonic, so
> > they fit in the native timeval. Nothing changes where both layouts are
> > the same.
> >
> > Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/290
> > Signed-off-by: Prasanth R <prasanth.r@lineo.co.jp>
> > ---
> > This applies on top of Paul's "v4l2: v4l2_compat: Handle __ioctl_time64"
> > (https://patchwork.libcamera.org/patch/25925/), which is needed for the
> > ioctls to reach the proxy at all.
> >
> > Tested on a Raspberry Pi 4 with a 32-bit, 64-bit time_t userspace (Yocto)
> > and a UVC camera, libcamera v0.4.0 with both patches:
> > libcamerify v4l2-ctl --stream-mmap --stream-count=30 streams at 15 fps,
> > 5/5 runs; without this patch VIDIOC_QUERYBUF fails with ENOTTY.
>
> Our of curiosity, why are you running a 32-bit userspace with 64-bit
> time_t on a RPi 4 ?
>
> >  src/v4l2/v4l2_camera_proxy.cpp | 76 ++++++++++++++++++++++++++++++++++
> >  1 file changed, 76 insertions(+)
> >
> > diff --git a/src/v4l2/v4l2_camera_proxy.cpp
> b/src/v4l2/v4l2_camera_proxy.cpp
> > index 559ffc617..554e81f8d 100644
> > --- a/src/v4l2/v4l2_camera_proxy.cpp
> > +++ b/src/v4l2/v4l2_camera_proxy.cpp
> > @@ -11,6 +11,7 @@
> >  #include <errno.h>
> >  #include <numeric>
> >  #include <set>
> > +#include <stddef.h>
> >  #include <string.h>
> >  #include <sys/mman.h>
> >  #include <unistd.h>
> > @@ -39,6 +40,64 @@ using namespace std::literals::chrono_literals;
> >
> >  LOG_DECLARE_CATEGORY(V4L2Compat)
> >
> > +namespace {
> > +
> > +/*
> > + * On 32-bit platforms the V4L2 compatibility layer is built with a
> 32-bit
> > + * time_t (see src/v4l2/meson.build), while applications are commonly
> built
> > + * with _TIME_BITS=64. struct v4l2_buffer embeds a struct timeval, so
> the two
> > + * disagree on the structure size, and thus on the buffer ioctl
> numbers. Only
> > + * the timestamp differs, the fields before and after it are the same.
> > + */
> > +struct v4l2_buffer_time64 {
> > +     __u8 head[offsetof(struct v4l2_buffer, timestamp)];
> > +     struct {
> > +             long long tv_sec;
> > +             long long tv_usec;
> > +     } timestamp;
> > +     __u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct
> v4l2_buffer, timecode)];
> > +};
> > +
> > +/*
> > + * Return the native request for a 64-bit time_t buffer ioctl, or 0 if
> the
> > + * request is not one or both layouts are the same.
> > + */
> > +unsigned int bufferRequestFromTime64(unsigned int request)
> > +{
> > +     if (sizeof(struct v4l2_buffer_time64) == sizeof(struct
> v4l2_buffer))
> > +             return 0;
> > +
> > +     switch (request) {
> > +     case _IOWR('V', 9, struct v4l2_buffer_time64):  /* VIDIOC_QUERYBUF
> */
> > +     case _IOWR('V', 15, struct v4l2_buffer_time64): /* VIDIOC_QBUF */
> > +     case _IOWR('V', 17, struct v4l2_buffer_time64): /* VIDIOC_DQBUF */
> > +     case _IOWR('V', 93, struct v4l2_buffer_time64): /*
> VIDIOC_PREPARE_BUF */
> > +             return _IOWR('V', _IOC_NR(request), struct v4l2_buffer);
> > +     default:
> > +             return 0;
> > +     }
> > +}
> > +
> > +void bufferFromTime64(const struct v4l2_buffer_time64 &src,
> > +                   struct v4l2_buffer *dst)
> > +{
> > +     memcpy(dst, src.head, sizeof(src.head));
> > +     dst->timestamp.tv_sec = src.timestamp.tv_sec;
> > +     dst->timestamp.tv_usec = src.timestamp.tv_usec;
> > +     memcpy(&dst->timecode, src.tail, sizeof(src.tail));
> > +}
> > +
> > +void bufferToTime64(const struct v4l2_buffer &src,
> > +                 struct v4l2_buffer_time64 *dst)
> > +{
> > +     memcpy(dst->head, &src, sizeof(dst->head));
> > +     dst->timestamp.tv_sec = src.timestamp.tv_sec;
> > +     dst->timestamp.tv_usec = src.timestamp.tv_usec;
> > +     memcpy(dst->tail, &src.timecode, sizeof(dst->tail));
> > +}
> > +
> > +} /* namespace */
> > +
> >  V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
> >                                std::shared_ptr<Camera> camera)
> >       : refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
> > @@ -873,6 +932,20 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file,
> unsigned long longRequest, void
> >               return -1;
> >       }
> >
> > +     /*
> > +      * Translate buffer ioctls from applications built with a 64-bit
> > +      * time_t to the native struct v4l2_buffer layout.
> > +      */
> > +     struct v4l2_buffer_time64 *buf64 = nullptr;
> > +     struct v4l2_buffer buf = {};
> > +     unsigned int bufRequest = bufferRequestFromTime64(request);
> > +     if (bufRequest) {
> > +             buf64 = static_cast<struct v4l2_buffer_time64 *>(arg);
> > +             bufferFromTime64(*buf64, &buf);
> > +             request = bufRequest;
> > +             arg = &buf;
> > +     }
> > +
> >       if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
> >               errno = ENOTTY;
> >               return -1;
> > @@ -955,6 +1028,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file,
> unsigned long longRequest, void
> >               return -1;
> >       }
> >
> > +     if (buf64)
> > +             bufferToTime64(buf, buf64);
> > +
> >       return ret;
> >  }
> >
>
> --
> Regards,
>
> Laurent Pinchart
>

Patch
diff mbox series

diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 559ffc617..554e81f8d 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -11,6 +11,7 @@ 
 #include <errno.h>
 #include <numeric>
 #include <set>
+#include <stddef.h>
 #include <string.h>
 #include <sys/mman.h>
 #include <unistd.h>
@@ -39,6 +40,64 @@  using namespace std::literals::chrono_literals;
 
 LOG_DECLARE_CATEGORY(V4L2Compat)
 
+namespace {
+
+/*
+ * On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
+ * time_t (see src/v4l2/meson.build), while applications are commonly built
+ * with _TIME_BITS=64. struct v4l2_buffer embeds a struct timeval, so the two
+ * disagree on the structure size, and thus on the buffer ioctl numbers. Only
+ * the timestamp differs, the fields before and after it are the same.
+ */
+struct v4l2_buffer_time64 {
+	__u8 head[offsetof(struct v4l2_buffer, timestamp)];
+	struct {
+		long long tv_sec;
+		long long tv_usec;
+	} timestamp;
+	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
+};
+
+/*
+ * Return the native request for a 64-bit time_t buffer ioctl, or 0 if the
+ * request is not one or both layouts are the same.
+ */
+unsigned int bufferRequestFromTime64(unsigned int request)
+{
+	if (sizeof(struct v4l2_buffer_time64) == sizeof(struct v4l2_buffer))
+		return 0;
+
+	switch (request) {
+	case _IOWR('V', 9, struct v4l2_buffer_time64):	/* VIDIOC_QUERYBUF */
+	case _IOWR('V', 15, struct v4l2_buffer_time64):	/* VIDIOC_QBUF */
+	case _IOWR('V', 17, struct v4l2_buffer_time64):	/* VIDIOC_DQBUF */
+	case _IOWR('V', 93, struct v4l2_buffer_time64):	/* VIDIOC_PREPARE_BUF */
+		return _IOWR('V', _IOC_NR(request), struct v4l2_buffer);
+	default:
+		return 0;
+	}
+}
+
+void bufferFromTime64(const struct v4l2_buffer_time64 &src,
+		      struct v4l2_buffer *dst)
+{
+	memcpy(dst, src.head, sizeof(src.head));
+	dst->timestamp.tv_sec = src.timestamp.tv_sec;
+	dst->timestamp.tv_usec = src.timestamp.tv_usec;
+	memcpy(&dst->timecode, src.tail, sizeof(src.tail));
+}
+
+void bufferToTime64(const struct v4l2_buffer &src,
+		    struct v4l2_buffer_time64 *dst)
+{
+	memcpy(dst->head, &src, sizeof(dst->head));
+	dst->timestamp.tv_sec = src.timestamp.tv_sec;
+	dst->timestamp.tv_usec = src.timestamp.tv_usec;
+	memcpy(dst->tail, &src.timecode, sizeof(dst->tail));
+}
+
+} /* namespace */
+
 V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
 				 std::shared_ptr<Camera> camera)
 	: refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
@@ -873,6 +932,20 @@  int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
 		return -1;
 	}
 
+	/*
+	 * Translate buffer ioctls from applications built with a 64-bit
+	 * time_t to the native struct v4l2_buffer layout.
+	 */
+	struct v4l2_buffer_time64 *buf64 = nullptr;
+	struct v4l2_buffer buf = {};
+	unsigned int bufRequest = bufferRequestFromTime64(request);
+	if (bufRequest) {
+		buf64 = static_cast<struct v4l2_buffer_time64 *>(arg);
+		bufferFromTime64(*buf64, &buf);
+		request = bufRequest;
+		arg = &buf;
+	}
+
 	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
 		errno = ENOTTY;
 		return -1;
@@ -955,6 +1028,9 @@  int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
 		return -1;
 	}
 
+	if (buf64)
+		bufferToTime64(buf, buf64);
+
 	return ret;
 }