[v2,2/2] v4l2: v4l2_camera_proxy: Handle 64-bit time_t buffer ioctls
diff mbox series

Message ID 20261023041954.913108-2-ragaprasanth@gmail.com
State Superseded
Headers show
Series
  • [v2,1/2] v4l2: v4l2_compat: Handle __ioctl_time64
Related show

Commit Message

Prasanth Ramesh Oct. 23, 2026, 4:19 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 <ragaprasanth@gmail.com>
---
Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.

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

Comments

Barnabás Pőcze Oct. 23, 2026, 7:35 p.m. UTC | #1
Hi

2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> 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 <ragaprasanth@gmail.com>
> ---
> Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> 
>   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 210c8e87..89e95198 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)];

__u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.
And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
on every architecture, can a `static_assert()` be added about the size?


> +};
> +
> +/*
> + * 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 */

I'd do
  
   case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):

instead of the comment.


> +	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),
> @@ -875,6 +934,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;
> +	}

I think it would be preferable to use the 64-bit version internally. And then
convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.

---

This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
Have you seen it?


> +
>   	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
>   		errno = ENOTTY;
>   		return -1;
> @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
>   		return -1;
>   	}
>   
> +	if (buf64)
> +		bufferToTime64(buf, buf64);
> +
>   	return ret;
>   }
>
Laurent Pinchart Oct. 27, 2026, 10:55 p.m. UTC | #2
On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
> 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> > 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 <ragaprasanth@gmail.com>
> > ---
> > Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> > time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> > 
> >   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 210c8e87..89e95198 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;

Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).

> > +	} timestamp;
> > +	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
> 
> __u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.

As this is a kernel structure it could be argued that kernel types are
appropriate, but I would only do so if we defined a full copy of the
structure (basically copying the struct v4l2_buffer32 definition from
drivers/media/v4l2-core/v4l2-compat-ioctl32.c).

> And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
> on every architecture, can a `static_assert()` be added about the size?

Good idea.


Different but related issue, I've checked the structure layout in 32-bit
binaries with 32-bit and 64-bit time_t by compiling the following code
and using pahole.

--------
#include <linux/videodev2.h>

struct v4l2_buffer buf;
--------

$ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32 -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
$ pahole v4l2_buffer.o
struct timeval {
        __time_t                   tv_sec;               /*     0     4 */
        __suseconds_t              tv_usec;              /*     4     4 */

        /* size: 8, cachelines: 1, members: 2 */
        /* last cacheline: 8 bytes */
};
struct v4l2_timecode {
        __u32                      type;                 /*     0     4 */
        __u32                      flags;                /*     4     4 */
        __u8                       frames;               /*     8     1 */
        __u8                       seconds;              /*     9     1 */
        __u8                       minutes;              /*    10     1 */
        __u8                       hours;                /*    11     1 */
        __u8                       userbits[4];          /*    12     4 */

        /* size: 16, cachelines: 1, members: 7 */
        /* last cacheline: 16 bytes */
};
struct v4l2_plane {
        __u32                      bytesused;            /*     0     4 */
        __u32                      length;               /*     4     4 */
        union {
                __u32              mem_offset;           /*     8     4 */
                long unsigned int  userptr;              /*     8     4 */
                __s32              fd;                   /*     8     4 */
        } m;                                             /*     8     4 */
        __u32                      data_offset;          /*    12     4 */
        __u32                      reserved[11];         /*    16    44 */

        /* size: 60, cachelines: 1, members: 5 */
        /* last cacheline: 60 bytes */
};
struct v4l2_buffer {
        __u32                      index;                /*     0     4 */
        __u32                      type;                 /*     4     4 */
        __u32                      bytesused;            /*     8     4 */
        __u32                      flags;                /*    12     4 */
        __u32                      field;                /*    16     4 */
        struct timeval             timestamp;            /*    20     8 */
        struct v4l2_timecode       timecode;             /*    28    16 */
        __u32                      sequence;             /*    44     4 */
        __u32                      memory;               /*    48     4 */
        union {
                __u32              offset;               /*    52     4 */
                long unsigned int  userptr;              /*    52     4 */
                struct v4l2_plane * planes;              /*    52     4 */
                __s32              fd;                   /*    52     4 */
        } m;                                             /*    52     4 */
        __u32                      length;               /*    56     4 */
        __u32                      reserved2;            /*    60     4 */
        /* --- cacheline 1 boundary (64 bytes) --- */
        union {
                __s32              request_fd;           /*    64     4 */
                __u32              reserved;             /*    64     4 */
        };                                               /*    64     4 */

        /* size: 68, cachelines: 2, members: 13 */
        /* last cacheline: 4 bytes */
};

$ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
$ pahole v4l2_buffer.o 
struct timeval {
        __time_t                   tv_sec;               /*     0     4 */
        __suseconds_t              tv_usec;              /*     4     4 */

        /* size: 8, cachelines: 1, members: 2 */
        /* last cacheline: 8 bytes */
};
struct v4l2_timecode {
        __u32                      type;                 /*     0     4 */
        __u32                      flags;                /*     4     4 */
        __u8                       frames;               /*     8     1 */
        __u8                       seconds;              /*     9     1 */
        __u8                       minutes;              /*    10     1 */
        __u8                       hours;                /*    11     1 */
        __u8                       userbits[4];          /*    12     4 */

        /* size: 16, cachelines: 1, members: 7 */
        /* last cacheline: 16 bytes */
};
struct v4l2_plane {
        __u32                      bytesused;            /*     0     4 */
        __u32                      length;               /*     4     4 */
        union {
                __u32              mem_offset;           /*     8     4 */
                long unsigned int  userptr;              /*     8     4 */
                __s32              fd;                   /*     8     4 */
        } m;                                             /*     8     4 */
        __u32                      data_offset;          /*    12     4 */
        __u32                      reserved[11];         /*    16    44 */

        /* size: 60, cachelines: 1, members: 5 */
        /* last cacheline: 60 bytes */
};
struct v4l2_buffer {
        __u32                      index;                /*     0     4 */
        __u32                      type;                 /*     4     4 */
        __u32                      bytesused;            /*     8     4 */
        __u32                      flags;                /*    12     4 */
        __u32                      field;                /*    16     4 */
        struct timeval             timestamp;            /*    20     8 */
        struct v4l2_timecode       timecode;             /*    28    16 */
        __u32                      sequence;             /*    44     4 */
        __u32                      memory;               /*    48     4 */
        union {
                __u32              offset;               /*    52     4 */
                long unsigned int  userptr;              /*    52     4 */
                struct v4l2_plane * planes;              /*    52     4 */
                __s32              fd;                   /*    52     4 */
        } m;                                             /*    52     4 */
        __u32                      length;               /*    56     4 */
        __u32                      reserved2;            /*    60     4 */
        /* --- cacheline 1 boundary (64 bytes) --- */
        union {
                __s32              request_fd;           /*    64     4 */
                __u32              reserved;             /*    64     4 */
        };                                               /*    64     4 */

        /* size: 68, cachelines: 2, members: 13 */
        /* last cacheline: 4 bytes */
};

The timestamp field is located at the same offset, so the
v4l2_buffer_time64 structure definition should be fine on 32-bit
platforms.

> > +};
> > +
> > +/*
> > + * 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 */
> 
> I'd do
>   
>    case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):
> 
> instead of the comment.
> 
> > +	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),
> > @@ -875,6 +934,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;
> > +	}
> 
> I think it would be preferable to use the 64-bit version internally. And then
> convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.

We would then need to fully define v4l2_buffer_time64 and use that
internally, wouldn't we ? That is doable, but more complex. If we want
to go that route, I would actually like to experiment with the
suggestion I made in https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
the compat layer with 64-bit time_t. That would give us a v4l2_buffer
that uses 64-bit time_t internally, and the translation would only be
required for applications that use 32-bit time_t. We could even consider
that those are not worth supporting and avoid the translation
completely. This should still solve Prasanth's issue if I'm not
mistaken.

> ---
> 
> This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> Have you seen it?
> 
> 
> > +
> >   	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
> >   		errno = ENOTTY;
> >   		return -1;
> > @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
> >   		return -1;
> >   	}
> >   
> > +	if (buf64)
> > +		bufferToTime64(buf, buf64);
> > +
> >   	return ret;
> >   }
> >
Laurent Pinchart Oct. 27, 2026, 11:43 p.m. UTC | #3
On Wed, Oct 28, 2026 at 12:55:32AM +0200, Laurent Pinchart wrote:
> On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
> > 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> > > 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 <ragaprasanth@gmail.com>
> > > ---
> > > Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> > > time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> > > 
> > >   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 210c8e87..89e95198 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;
> 
> Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
> both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).
> 
> > > +	} timestamp;
> > > +	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
> > 
> > __u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.
> 
> As this is a kernel structure it could be argued that kernel types are
> appropriate, but I would only do so if we defined a full copy of the
> structure (basically copying the struct v4l2_buffer32 definition from
> drivers/media/v4l2-core/v4l2-compat-ioctl32.c).
> 
> > And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
> > on every architecture, can a `static_assert()` be added about the size?
> 
> Good idea.
> 
> 
> Different but related issue, I've checked the structure layout in 32-bit
> binaries with 32-bit and 64-bit time_t by compiling the following code
> and using pahole.
> 
> --------
> #include <linux/videodev2.h>
> 
> struct v4l2_buffer buf;
> --------
> 
> $ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32 -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
> $ pahole v4l2_buffer.o
> struct timeval {
>         __time_t                   tv_sec;               /*     0     4 */
>         __suseconds_t              tv_usec;              /*     4     4 */
> 
>         /* size: 8, cachelines: 1, members: 2 */
>         /* last cacheline: 8 bytes */
> };
> struct v4l2_timecode {
>         __u32                      type;                 /*     0     4 */
>         __u32                      flags;                /*     4     4 */
>         __u8                       frames;               /*     8     1 */
>         __u8                       seconds;              /*     9     1 */
>         __u8                       minutes;              /*    10     1 */
>         __u8                       hours;                /*    11     1 */
>         __u8                       userbits[4];          /*    12     4 */
> 
>         /* size: 16, cachelines: 1, members: 7 */
>         /* last cacheline: 16 bytes */
> };
> struct v4l2_plane {
>         __u32                      bytesused;            /*     0     4 */
>         __u32                      length;               /*     4     4 */
>         union {
>                 __u32              mem_offset;           /*     8     4 */
>                 long unsigned int  userptr;              /*     8     4 */
>                 __s32              fd;                   /*     8     4 */
>         } m;                                             /*     8     4 */
>         __u32                      data_offset;          /*    12     4 */
>         __u32                      reserved[11];         /*    16    44 */
> 
>         /* size: 60, cachelines: 1, members: 5 */
>         /* last cacheline: 60 bytes */
> };
> struct v4l2_buffer {
>         __u32                      index;                /*     0     4 */
>         __u32                      type;                 /*     4     4 */
>         __u32                      bytesused;            /*     8     4 */
>         __u32                      flags;                /*    12     4 */
>         __u32                      field;                /*    16     4 */
>         struct timeval             timestamp;            /*    20     8 */
>         struct v4l2_timecode       timecode;             /*    28    16 */
>         __u32                      sequence;             /*    44     4 */
>         __u32                      memory;               /*    48     4 */
>         union {
>                 __u32              offset;               /*    52     4 */
>                 long unsigned int  userptr;              /*    52     4 */
>                 struct v4l2_plane * planes;              /*    52     4 */
>                 __s32              fd;                   /*    52     4 */
>         } m;                                             /*    52     4 */
>         __u32                      length;               /*    56     4 */
>         __u32                      reserved2;            /*    60     4 */
>         /* --- cacheline 1 boundary (64 bytes) --- */
>         union {
>                 __s32              request_fd;           /*    64     4 */
>                 __u32              reserved;             /*    64     4 */
>         };                                               /*    64     4 */
> 
>         /* size: 68, cachelines: 2, members: 13 */
>         /* last cacheline: 4 bytes */
> };
> 
> $ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
> $ pahole v4l2_buffer.o 
> struct timeval {
>         __time_t                   tv_sec;               /*     0     4 */
>         __suseconds_t              tv_usec;              /*     4     4 */
> 
>         /* size: 8, cachelines: 1, members: 2 */
>         /* last cacheline: 8 bytes */
> };
> struct v4l2_timecode {
>         __u32                      type;                 /*     0     4 */
>         __u32                      flags;                /*     4     4 */
>         __u8                       frames;               /*     8     1 */
>         __u8                       seconds;              /*     9     1 */
>         __u8                       minutes;              /*    10     1 */
>         __u8                       hours;                /*    11     1 */
>         __u8                       userbits[4];          /*    12     4 */
> 
>         /* size: 16, cachelines: 1, members: 7 */
>         /* last cacheline: 16 bytes */
> };
> struct v4l2_plane {
>         __u32                      bytesused;            /*     0     4 */
>         __u32                      length;               /*     4     4 */
>         union {
>                 __u32              mem_offset;           /*     8     4 */
>                 long unsigned int  userptr;              /*     8     4 */
>                 __s32              fd;                   /*     8     4 */
>         } m;                                             /*     8     4 */
>         __u32                      data_offset;          /*    12     4 */
>         __u32                      reserved[11];         /*    16    44 */
> 
>         /* size: 60, cachelines: 1, members: 5 */
>         /* last cacheline: 60 bytes */
> };
> struct v4l2_buffer {
>         __u32                      index;                /*     0     4 */
>         __u32                      type;                 /*     4     4 */
>         __u32                      bytesused;            /*     8     4 */
>         __u32                      flags;                /*    12     4 */
>         __u32                      field;                /*    16     4 */
>         struct timeval             timestamp;            /*    20     8 */
>         struct v4l2_timecode       timecode;             /*    28    16 */
>         __u32                      sequence;             /*    44     4 */
>         __u32                      memory;               /*    48     4 */
>         union {
>                 __u32              offset;               /*    52     4 */
>                 long unsigned int  userptr;              /*    52     4 */
>                 struct v4l2_plane * planes;              /*    52     4 */
>                 __s32              fd;                   /*    52     4 */
>         } m;                                             /*    52     4 */
>         __u32                      length;               /*    56     4 */
>         __u32                      reserved2;            /*    60     4 */
>         /* --- cacheline 1 boundary (64 bytes) --- */
>         union {
>                 __s32              request_fd;           /*    64     4 */
>                 __u32              reserved;             /*    64     4 */
>         };                                               /*    64     4 */
> 
>         /* size: 68, cachelines: 2, members: 13 */
>         /* last cacheline: 4 bytes */
> };
> 
> The timestamp field is located at the same offset, so the
> v4l2_buffer_time64 structure definition should be fine on 32-bit
> platforms.
> 
> > > +};
> > > +
> > > +/*
> > > + * 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 */
> > 
> > I'd do
> >   
> >    case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):
> > 
> > instead of the comment.
> > 
> > > +	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),
> > > @@ -875,6 +934,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;
> > > +	}
> > 
> > I think it would be preferable to use the 64-bit version internally. And then
> > convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.
> 
> We would then need to fully define v4l2_buffer_time64 and use that
> internally, wouldn't we ? That is doable, but more complex. If we want
> to go that route, I would actually like to experiment with the
> suggestion I made in https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
> the compat layer with 64-bit time_t. That would give us a v4l2_buffer
> that uses 64-bit time_t internally, and the translation would only be
> required for applications that use 32-bit time_t. We could even consider
> that those are not worth supporting and avoid the translation
> completely. This should still solve Prasanth's issue if I'm not
> mistaken.

I'm thinking about something like

diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build
index 2c0404147183..8674040f121f 100644
--- a/src/v4l2/meson.build
+++ b/src/v4l2/meson.build
@@ -10,10 +10,13 @@ v4l2_compat_sources = files([
     'v4l2_camera.cpp',
     'v4l2_camera_file.cpp',
     'v4l2_camera_proxy.cpp',
-    'v4l2_compat.cpp',
     'v4l2_compat_manager.cpp',
 ])

+v4l2_compat_cpp_sources = files([
+    'v4l2_compat.cpp',
+])
+
 v4l2_compat_cpp_args = [
     # Meson enables large file support unconditionally, which redirect file
     # operations to 64-bit versions. This results in some symbols being
@@ -27,9 +30,16 @@ v4l2_compat_cpp_args = [
     '-fvisibility=hidden',
 ]

+
+v4l2_compat_core = static_library('v4l2-compat-core',
+                                  v4l2_compat_sources,
+                                  install : false,
+                                  dependencies : [libcamera_private, libdl])
+
 v4l2_compat = shared_library('v4l2-compat',
-                             v4l2_compat_sources,
+                             v4l2_compat_cpp_sources,
                              name_prefix : '',
+                             link_with : v4l2_compat_core,
                              install : true,
                              install_dir : libcamera_libexecdir,
                              dependencies : [libcamera_private, libdl],

It may be possible to clean that up a bit.

> > ---
> > 
> > This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > Have you seen it?
> > 
> > 
> > > +
> > >   	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
> > >   		errno = ENOTTY;
> > >   		return -1;
> > > @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
> > >   		return -1;
> > >   	}
> > >   
> > > +	if (buf64)
> > > +		bufferToTime64(buf, buf64);
> > > +
> > >   	return ret;
> > >   }
> > >
Prasanth Ramesh Oct. 28, 2026, 3:22 a.m. UTC | #4
Hi Laurent,

Thanks for the review.
Building only v4l2_compat.cpp with 32-bit time_t is much simpler.
I'll try, test, and send a v3.

Thanks,
Prasanth R


On Wed, Oct 28, 2026 at 8:43 AM Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> On Wed, Oct 28, 2026 at 12:55:32AM +0200, Laurent Pinchart wrote:
> > On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
> > > 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> > > > 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 <ragaprasanth@gmail.com>
> > > > ---
> > > > Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> > > > time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> > > >
> > > >   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 210c8e87..89e95198 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;
> >
> > Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
> > both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).
> >
> > > > + } timestamp;
> > > > + __u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct
> v4l2_buffer, timecode)];
> > >
> > > __u8 is a linux-ism, I think `std::byte` or even `unsigned char`,
> `uint8_t` would be preferable.
> >
> > As this is a kernel structure it could be argued that kernel types are
> > appropriate, but I would only do so if we defined a full copy of the
> > structure (basically copying the struct v4l2_buffer32 definition from
> > drivers/media/v4l2-core/v4l2-compat-ioctl32.c).
> >
> > > And is this appropriate for all platforms? Does `v4l2_buffer` have the
> same size with 64-bit
> > > on every architecture, can a `static_assert()` be added about the size?
> >
> > Good idea.
> >
> >
> > Different but related issue, I've checked the structure layout in 32-bit
> > binaries with 32-bit and 64-bit time_t by compiling the following code
> > and using pahole.
> >
> > --------
> > #include <linux/videodev2.h>
> >
> > struct v4l2_buffer buf;
> > --------
> >
> > $ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32
> -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
> > $ pahole v4l2_buffer.o
> > struct timeval {
> >         __time_t                   tv_sec;               /*     0     4
> */
> >         __suseconds_t              tv_usec;              /*     4     4
> */
> >
> >         /* size: 8, cachelines: 1, members: 2 */
> >         /* last cacheline: 8 bytes */
> > };
> > struct v4l2_timecode {
> >         __u32                      type;                 /*     0     4
> */
> >         __u32                      flags;                /*     4     4
> */
> >         __u8                       frames;               /*     8     1
> */
> >         __u8                       seconds;              /*     9     1
> */
> >         __u8                       minutes;              /*    10     1
> */
> >         __u8                       hours;                /*    11     1
> */
> >         __u8                       userbits[4];          /*    12     4
> */
> >
> >         /* size: 16, cachelines: 1, members: 7 */
> >         /* last cacheline: 16 bytes */
> > };
> > struct v4l2_plane {
> >         __u32                      bytesused;            /*     0     4
> */
> >         __u32                      length;               /*     4     4
> */
> >         union {
> >                 __u32              mem_offset;           /*     8     4
> */
> >                 long unsigned int  userptr;              /*     8     4
> */
> >                 __s32              fd;                   /*     8     4
> */
> >         } m;                                             /*     8     4
> */
> >         __u32                      data_offset;          /*    12     4
> */
> >         __u32                      reserved[11];         /*    16    44
> */
> >
> >         /* size: 60, cachelines: 1, members: 5 */
> >         /* last cacheline: 60 bytes */
> > };
> > struct v4l2_buffer {
> >         __u32                      index;                /*     0     4
> */
> >         __u32                      type;                 /*     4     4
> */
> >         __u32                      bytesused;            /*     8     4
> */
> >         __u32                      flags;                /*    12     4
> */
> >         __u32                      field;                /*    16     4
> */
> >         struct timeval             timestamp;            /*    20     8
> */
> >         struct v4l2_timecode       timecode;             /*    28    16
> */
> >         __u32                      sequence;             /*    44     4
> */
> >         __u32                      memory;               /*    48     4
> */
> >         union {
> >                 __u32              offset;               /*    52     4
> */
> >                 long unsigned int  userptr;              /*    52     4
> */
> >                 struct v4l2_plane * planes;              /*    52     4
> */
> >                 __s32              fd;                   /*    52     4
> */
> >         } m;                                             /*    52     4
> */
> >         __u32                      length;               /*    56     4
> */
> >         __u32                      reserved2;            /*    60     4
> */
> >         /* --- cacheline 1 boundary (64 bytes) --- */
> >         union {
> >                 __s32              request_fd;           /*    64     4
> */
> >                 __u32              reserved;             /*    64     4
> */
> >         };                                               /*    64     4
> */
> >
> >         /* size: 68, cachelines: 2, members: 13 */
> >         /* last cacheline: 4 bytes */
> > };
> >
> > $ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
> > $ pahole v4l2_buffer.o
> > struct timeval {
> >         __time_t                   tv_sec;               /*     0     4
> */
> >         __suseconds_t              tv_usec;              /*     4     4
> */
> >
> >         /* size: 8, cachelines: 1, members: 2 */
> >         /* last cacheline: 8 bytes */
> > };
> > struct v4l2_timecode {
> >         __u32                      type;                 /*     0     4
> */
> >         __u32                      flags;                /*     4     4
> */
> >         __u8                       frames;               /*     8     1
> */
> >         __u8                       seconds;              /*     9     1
> */
> >         __u8                       minutes;              /*    10     1
> */
> >         __u8                       hours;                /*    11     1
> */
> >         __u8                       userbits[4];          /*    12     4
> */
> >
> >         /* size: 16, cachelines: 1, members: 7 */
> >         /* last cacheline: 16 bytes */
> > };
> > struct v4l2_plane {
> >         __u32                      bytesused;            /*     0     4
> */
> >         __u32                      length;               /*     4     4
> */
> >         union {
> >                 __u32              mem_offset;           /*     8     4
> */
> >                 long unsigned int  userptr;              /*     8     4
> */
> >                 __s32              fd;                   /*     8     4
> */
> >         } m;                                             /*     8     4
> */
> >         __u32                      data_offset;          /*    12     4
> */
> >         __u32                      reserved[11];         /*    16    44
> */
> >
> >         /* size: 60, cachelines: 1, members: 5 */
> >         /* last cacheline: 60 bytes */
> > };
> > struct v4l2_buffer {
> >         __u32                      index;                /*     0     4
> */
> >         __u32                      type;                 /*     4     4
> */
> >         __u32                      bytesused;            /*     8     4
> */
> >         __u32                      flags;                /*    12     4
> */
> >         __u32                      field;                /*    16     4
> */
> >         struct timeval             timestamp;            /*    20     8
> */
> >         struct v4l2_timecode       timecode;             /*    28    16
> */
> >         __u32                      sequence;             /*    44     4
> */
> >         __u32                      memory;               /*    48     4
> */
> >         union {
> >                 __u32              offset;               /*    52     4
> */
> >                 long unsigned int  userptr;              /*    52     4
> */
> >                 struct v4l2_plane * planes;              /*    52     4
> */
> >                 __s32              fd;                   /*    52     4
> */
> >         } m;                                             /*    52     4
> */
> >         __u32                      length;               /*    56     4
> */
> >         __u32                      reserved2;            /*    60     4
> */
> >         /* --- cacheline 1 boundary (64 bytes) --- */
> >         union {
> >                 __s32              request_fd;           /*    64     4
> */
> >                 __u32              reserved;             /*    64     4
> */
> >         };                                               /*    64     4
> */
> >
> >         /* size: 68, cachelines: 2, members: 13 */
> >         /* last cacheline: 4 bytes */
> > };
> >
> > The timestamp field is located at the same offset, so the
> > v4l2_buffer_time64 structure definition should be fine on 32-bit
> > platforms.
> >
> > > > +};
> > > > +
> > > > +/*
> > > > + * 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
> */
> > >
> > > I'd do
> > >
> > >    case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct
> v4l2_buffer_time64):
> > >
> > > instead of the comment.
> > >
> > > > + 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),
> > > > @@ -875,6 +934,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;
> > > > + }
> > >
> > > I think it would be preferable to use the 64-bit version internally.
> And then
> > > convert the timestamp at the end, if it cannot be converted, return
> EOVERFLOW.
> >
> > We would then need to fully define v4l2_buffer_time64 and use that
> > internally, wouldn't we ? That is doable, but more complex. If we want
> > to go that route, I would actually like to experiment with the
> > suggestion I made in
> https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
> > the compat layer with 64-bit time_t. That would give us a v4l2_buffer
> > that uses 64-bit time_t internally, and the translation would only be
> > required for applications that use 32-bit time_t. We could even consider
> > that those are not worth supporting and avoid the translation
> > completely. This should still solve Prasanth's issue if I'm not
> > mistaken.
>
> I'm thinking about something like
>
> diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build
> index 2c0404147183..8674040f121f 100644
> --- a/src/v4l2/meson.build
> +++ b/src/v4l2/meson.build
> @@ -10,10 +10,13 @@ v4l2_compat_sources = files([
>      'v4l2_camera.cpp',
>      'v4l2_camera_file.cpp',
>      'v4l2_camera_proxy.cpp',
> -    'v4l2_compat.cpp',
>      'v4l2_compat_manager.cpp',
>  ])
>
> +v4l2_compat_cpp_sources = files([
> +    'v4l2_compat.cpp',
> +])
> +
>  v4l2_compat_cpp_args = [
>      # Meson enables large file support unconditionally, which redirect
> file
>      # operations to 64-bit versions. This results in some symbols being
> @@ -27,9 +30,16 @@ v4l2_compat_cpp_args = [
>      '-fvisibility=hidden',
>  ]
>
> +
> +v4l2_compat_core = static_library('v4l2-compat-core',
> +                                  v4l2_compat_sources,
> +                                  install : false,
> +                                  dependencies : [libcamera_private,
> libdl])
> +
>  v4l2_compat = shared_library('v4l2-compat',
> -                             v4l2_compat_sources,
> +                             v4l2_compat_cpp_sources,
>                               name_prefix : '',
> +                             link_with : v4l2_compat_core,
>                               install : true,
>                               install_dir : libcamera_libexecdir,
>                               dependencies : [libcamera_private, libdl],
>
> It may be possible to clean that up a bit.
>
> > > ---
> > >
> > > This is also related to
> https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > > Have you seen it?
> > >
> > >
> > > > +
> > > >           if (supportedIoctls_.find(request) ==
> supportedIoctls_.end()) {
> > > >                   errno = ENOTTY;
> > > >                   return -1;
> > > > @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile
> *file, unsigned long longRequest, void
> > > >                   return -1;
> > > >           }
> > > >
> > > > + if (buf64)
> > > > +         bufferToTime64(buf, buf64);
> > > > +
> > > >           return ret;
> > > >   }
> > > >
>
> --
> Regards,
>
> Laurent Pinchart
>
Laurent Pinchart Oct. 28, 2026, 9 a.m. UTC | #5
Hi Prasanth,

On Wed, Oct 28, 2026 at 12:22:51PM +0900, Prasanth Ramesh wrote:
> Hi Laurent,
> 
> Thanks for the review.
> Building only v4l2_compat.cpp with 32-bit time_t is much simpler.
> I'll try, test, and send a v3.

The downside of my proposed solution is that it doesn't support
applications compiled with a different time_t size than the system's
default. In practical terms, that means

		| System default time_t
Application	| 32-bit	| 64-bit
time_t		|		|
-----------------------------------------------
32-bit		| works		| doesn't work
64-bit		| doesn't work	| works

32-bit time_t systems are, as far as I understand, increasingly rare. My
hope is that we won't need to care about those two hybrid cases. If we
need to support them, we can then add conversion code similar to your
patch on top.

Is my understand correct that your use cases do not require support for
hybrid 32/64 time_t ?

> On Wed, Oct 28, 2026 at 8:43 AM Laurent Pinchart wrote:
> > On Wed, Oct 28, 2026 at 12:55:32AM +0200, Laurent Pinchart wrote:
> > > On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
> > > > 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> > > > > 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 <ragaprasanth@gmail.com>
> > > > > ---
> > > > > Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> > > > > time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> > > > >
> > > > >   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 210c8e87..89e95198 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;
> > >
> > > Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
> > > both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).
> > >
> > > > > + } timestamp;
> > > > > + __u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
> > > >
> > > > __u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.
> > >
> > > As this is a kernel structure it could be argued that kernel types are
> > > appropriate, but I would only do so if we defined a full copy of the
> > > structure (basically copying the struct v4l2_buffer32 definition from
> > > drivers/media/v4l2-core/v4l2-compat-ioctl32.c).
> > >
> > > > And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
> > > > on every architecture, can a `static_assert()` be added about the size?
> > >
> > > Good idea.
> > >
> > >
> > > Different but related issue, I've checked the structure layout in 32-bit
> > > binaries with 32-bit and 64-bit time_t by compiling the following code
> > > and using pahole.
> > >
> > > --------
> > > #include <linux/videodev2.h>
> > >
> > > struct v4l2_buffer buf;
> > > --------
> > >
> > > $ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32 -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
> > > $ pahole v4l2_buffer.o
> > > struct timeval {
> > >         __time_t                   tv_sec;               /*     0     4 */
> > >         __suseconds_t              tv_usec;              /*     4     4 */
> > >
> > >         /* size: 8, cachelines: 1, members: 2 */
> > >         /* last cacheline: 8 bytes */
> > > };
> > > struct v4l2_timecode {
> > >         __u32                      type;                 /*     0     4 */
> > >         __u32                      flags;                /*     4     4 */
> > >         __u8                       frames;               /*     8     1 */
> > >         __u8                       seconds;              /*     9     1 */
> > >         __u8                       minutes;              /*    10     1 */
> > >         __u8                       hours;                /*    11     1 */
> > >         __u8                       userbits[4];          /*    12     4 */
> > >
> > >         /* size: 16, cachelines: 1, members: 7 */
> > >         /* last cacheline: 16 bytes */
> > > };
> > > struct v4l2_plane {
> > >         __u32                      bytesused;            /*     0     4 */
> > >         __u32                      length;               /*     4     4 */
> > >         union {
> > >                 __u32              mem_offset;           /*     8     4 */
> > >                 long unsigned int  userptr;              /*     8     4 */
> > >                 __s32              fd;                   /*     8     4 */
> > >         } m;                                             /*     8     4 */
> > >         __u32                      data_offset;          /*    12     4 */
> > >         __u32                      reserved[11];         /*    16    44 */
> > >
> > >         /* size: 60, cachelines: 1, members: 5 */
> > >         /* last cacheline: 60 bytes */
> > > };
> > > struct v4l2_buffer {
> > >         __u32                      index;                /*     0     4 */
> > >         __u32                      type;                 /*     4     4 */
> > >         __u32                      bytesused;            /*     8     4 */
> > >         __u32                      flags;                /*    12     4 */
> > >         __u32                      field;                /*    16     4 */
> > >         struct timeval             timestamp;            /*    20     8 */
> > >         struct v4l2_timecode       timecode;             /*    28    16 */
> > >         __u32                      sequence;             /*    44     4 */
> > >         __u32                      memory;               /*    48     4 */
> > >         union {
> > >                 __u32              offset;               /*    52     4 */
> > >                 long unsigned int  userptr;              /*    52     4 */
> > >                 struct v4l2_plane * planes;              /*    52     4 */
> > >                 __s32              fd;                   /*    52     4 */
> > >         } m;                                             /*    52     4 */
> > >         __u32                      length;               /*    56     4 */
> > >         __u32                      reserved2;            /*    60     4 */
> > >         /* --- cacheline 1 boundary (64 bytes) --- */
> > >         union {
> > >                 __s32              request_fd;           /*    64     4 */
> > >                 __u32              reserved;             /*    64     4 */
> > >         };                                               /*    64     4 */
> > >
> > >         /* size: 68, cachelines: 2, members: 13 */
> > >         /* last cacheline: 4 bytes */
> > > };
> > >
> > > $ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
> > > $ pahole v4l2_buffer.o
> > > struct timeval {
> > >         __time_t                   tv_sec;               /*     0     4 */
> > >         __suseconds_t              tv_usec;              /*     4     4 */
> > >
> > >         /* size: 8, cachelines: 1, members: 2 */
> > >         /* last cacheline: 8 bytes */
> > > };
> > > struct v4l2_timecode {
> > >         __u32                      type;                 /*     0     4 */
> > >         __u32                      flags;                /*     4     4 */
> > >         __u8                       frames;               /*     8     1 */
> > >         __u8                       seconds;              /*     9     1 */
> > >         __u8                       minutes;              /*    10     1 */
> > >         __u8                       hours;                /*    11     1 */
> > >         __u8                       userbits[4];          /*    12     4 */
> > >
> > >         /* size: 16, cachelines: 1, members: 7 */
> > >         /* last cacheline: 16 bytes */
> > > };
> > > struct v4l2_plane {
> > >         __u32                      bytesused;            /*     0     4 */
> > >         __u32                      length;               /*     4     4 */
> > >         union {
> > >                 __u32              mem_offset;           /*     8     4 */
> > >                 long unsigned int  userptr;              /*     8     4 */
> > >                 __s32              fd;                   /*     8     4 */
> > >         } m;                                             /*     8     4 */
> > >         __u32                      data_offset;          /*    12     4 */
> > >         __u32                      reserved[11];         /*    16    44 */
> > >
> > >         /* size: 60, cachelines: 1, members: 5 */
> > >         /* last cacheline: 60 bytes */
> > > };
> > > struct v4l2_buffer {
> > >         __u32                      index;                /*     0     4 */
> > >         __u32                      type;                 /*     4     4 */
> > >         __u32                      bytesused;            /*     8     4 */
> > >         __u32                      flags;                /*    12     4 */
> > >         __u32                      field;                /*    16     4 */
> > >         struct timeval             timestamp;            /*    20     8 */
> > >         struct v4l2_timecode       timecode;             /*    28    16 */
> > >         __u32                      sequence;             /*    44     4 */
> > >         __u32                      memory;               /*    48     4 */
> > >         union {
> > >                 __u32              offset;               /*    52     4 */
> > >                 long unsigned int  userptr;              /*    52     4 */
> > >                 struct v4l2_plane * planes;              /*    52     4 */
> > >                 __s32              fd;                   /*    52     4 */
> > >         } m;                                             /*    52     4 */
> > >         __u32                      length;               /*    56     4 */
> > >         __u32                      reserved2;            /*    60     4 */
> > >         /* --- cacheline 1 boundary (64 bytes) --- */
> > >         union {
> > >                 __s32              request_fd;           /*    64     4 */
> > >                 __u32              reserved;             /*    64     4 */
> > >         };                                               /*    64     4 */
> > >
> > >         /* size: 68, cachelines: 2, members: 13 */
> > >         /* last cacheline: 4 bytes */
> > > };
> > >
> > > The timestamp field is located at the same offset, so the
> > > v4l2_buffer_time64 structure definition should be fine on 32-bit
> > > platforms.
> > >
> > > > > +};
> > > > > +
> > > > > +/*
> > > > > + * 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 */
> > > >
> > > > I'd do
> > > >
> > > >    case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):
> > > >
> > > > instead of the comment.
> > > >
> > > > > + 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),
> > > > > @@ -875,6 +934,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;
> > > > > + }
> > > >
> > > > I think it would be preferable to use the 64-bit version internally. And then
> > > > convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.
> > >
> > > We would then need to fully define v4l2_buffer_time64 and use that
> > > internally, wouldn't we ? That is doable, but more complex. If we want
> > > to go that route, I would actually like to experiment with the
> > > suggestion I made in https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > > of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
> > > the compat layer with 64-bit time_t. That would give us a v4l2_buffer
> > > that uses 64-bit time_t internally, and the translation would only be
> > > required for applications that use 32-bit time_t. We could even consider
> > > that those are not worth supporting and avoid the translation
> > > completely. This should still solve Prasanth's issue if I'm not
> > > mistaken.
> >
> > I'm thinking about something like
> >
> > diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build
> > index 2c0404147183..8674040f121f 100644
> > --- a/src/v4l2/meson.build
> > +++ b/src/v4l2/meson.build
> > @@ -10,10 +10,13 @@ v4l2_compat_sources = files([
> >      'v4l2_camera.cpp',
> >      'v4l2_camera_file.cpp',
> >      'v4l2_camera_proxy.cpp',
> > -    'v4l2_compat.cpp',
> >      'v4l2_compat_manager.cpp',
> >  ])
> >
> > +v4l2_compat_cpp_sources = files([
> > +    'v4l2_compat.cpp',
> > +])
> > +
> >  v4l2_compat_cpp_args = [
> >      # Meson enables large file support unconditionally, which redirect file
> >      # operations to 64-bit versions. This results in some symbols being
> > @@ -27,9 +30,16 @@ v4l2_compat_cpp_args = [
> >      '-fvisibility=hidden',
> >  ]
> >
> > +
> > +v4l2_compat_core = static_library('v4l2-compat-core',
> > +                                  v4l2_compat_sources,
> > +                                  install : false,
> > +                                  dependencies : [libcamera_private, libdl])
> > +
> >  v4l2_compat = shared_library('v4l2-compat',
> > -                             v4l2_compat_sources,
> > +                             v4l2_compat_cpp_sources,
> >                               name_prefix : '',
> > +                             link_with : v4l2_compat_core,
> >                               install : true,
> >                               install_dir : libcamera_libexecdir,
> >                               dependencies : [libcamera_private, libdl],
> >
> > It may be possible to clean that up a bit.
> >
> > > > ---
> > > >
> > > > This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > > > Have you seen it?
> > > >
> > > > > +
> > > > >           if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
> > > > >                   errno = ENOTTY;
> > > > >                   return -1;
> > > > > @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
> > > > >                   return -1;
> > > > >           }
> > > > >
> > > > > + if (buf64)
> > > > > +         bufferToTime64(buf, buf64);
> > > > > +
> > > > >           return ret;
> > > > >   }
> > > > >
Barnabás Pőcze Oct. 28, 2026, 9:09 a.m. UTC | #6
2026. 10. 27. 23:55 keltezéssel, Laurent Pinchart írta:
> On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
>> 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
>>> 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 <ragaprasanth@gmail.com>
>>> ---
>>> Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
>>> time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
>>>
>>>    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 210c8e87..89e95198 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;
> 
> Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
> both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).
> 
>>> +	} timestamp;
>>> +	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
>>
>> __u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.
> 
> As this is a kernel structure it could be argued that kernel types are
> appropriate, but I would only do so if we defined a full copy of the
> structure (basically copying the struct v4l2_buffer32 definition from
> drivers/media/v4l2-core/v4l2-compat-ioctl32.c).
> 
>> And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
>> on every architecture, can a `static_assert()` be added about the size?
> 
> Good idea.
> 
> 
> Different but related issue, I've checked the structure layout in 32-bit
> binaries with 32-bit and 64-bit time_t by compiling the following code
> and using pahole.
> 
> --------
> #include <linux/videodev2.h>
> 
> struct v4l2_buffer buf;
> --------
> 
> $ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32 -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
> $ pahole v4l2_buffer.o
> struct timeval {
>          __time_t                   tv_sec;               /*     0     4 */
>          __suseconds_t              tv_usec;              /*     4     4 */
> 
>          /* size: 8, cachelines: 1, members: 2 */
>          /* last cacheline: 8 bytes */
> };
> struct v4l2_timecode {
>          __u32                      type;                 /*     0     4 */
>          __u32                      flags;                /*     4     4 */
>          __u8                       frames;               /*     8     1 */
>          __u8                       seconds;              /*     9     1 */
>          __u8                       minutes;              /*    10     1 */
>          __u8                       hours;                /*    11     1 */
>          __u8                       userbits[4];          /*    12     4 */
> 
>          /* size: 16, cachelines: 1, members: 7 */
>          /* last cacheline: 16 bytes */
> };
> struct v4l2_plane {
>          __u32                      bytesused;            /*     0     4 */
>          __u32                      length;               /*     4     4 */
>          union {
>                  __u32              mem_offset;           /*     8     4 */
>                  long unsigned int  userptr;              /*     8     4 */
>                  __s32              fd;                   /*     8     4 */
>          } m;                                             /*     8     4 */
>          __u32                      data_offset;          /*    12     4 */
>          __u32                      reserved[11];         /*    16    44 */
> 
>          /* size: 60, cachelines: 1, members: 5 */
>          /* last cacheline: 60 bytes */
> };
> struct v4l2_buffer {
>          __u32                      index;                /*     0     4 */
>          __u32                      type;                 /*     4     4 */
>          __u32                      bytesused;            /*     8     4 */
>          __u32                      flags;                /*    12     4 */
>          __u32                      field;                /*    16     4 */
>          struct timeval             timestamp;            /*    20     8 */
>          struct v4l2_timecode       timecode;             /*    28    16 */
>          __u32                      sequence;             /*    44     4 */
>          __u32                      memory;               /*    48     4 */
>          union {
>                  __u32              offset;               /*    52     4 */
>                  long unsigned int  userptr;              /*    52     4 */
>                  struct v4l2_plane * planes;              /*    52     4 */
>                  __s32              fd;                   /*    52     4 */
>          } m;                                             /*    52     4 */
>          __u32                      length;               /*    56     4 */
>          __u32                      reserved2;            /*    60     4 */
>          /* --- cacheline 1 boundary (64 bytes) --- */
>          union {
>                  __s32              request_fd;           /*    64     4 */
>                  __u32              reserved;             /*    64     4 */
>          };                                               /*    64     4 */
> 
>          /* size: 68, cachelines: 2, members: 13 */
>          /* last cacheline: 4 bytes */
> };
> 
> $ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
> $ pahole v4l2_buffer.o
> struct timeval {
>          __time_t                   tv_sec;               /*     0     4 */
>          __suseconds_t              tv_usec;              /*     4     4 */
> 
>          /* size: 8, cachelines: 1, members: 2 */
>          /* last cacheline: 8 bytes */
> };
> struct v4l2_timecode {
>          __u32                      type;                 /*     0     4 */
>          __u32                      flags;                /*     4     4 */
>          __u8                       frames;               /*     8     1 */
>          __u8                       seconds;              /*     9     1 */
>          __u8                       minutes;              /*    10     1 */
>          __u8                       hours;                /*    11     1 */
>          __u8                       userbits[4];          /*    12     4 */
> 
>          /* size: 16, cachelines: 1, members: 7 */
>          /* last cacheline: 16 bytes */
> };
> struct v4l2_plane {
>          __u32                      bytesused;            /*     0     4 */
>          __u32                      length;               /*     4     4 */
>          union {
>                  __u32              mem_offset;           /*     8     4 */
>                  long unsigned int  userptr;              /*     8     4 */
>                  __s32              fd;                   /*     8     4 */
>          } m;                                             /*     8     4 */
>          __u32                      data_offset;          /*    12     4 */
>          __u32                      reserved[11];         /*    16    44 */
> 
>          /* size: 60, cachelines: 1, members: 5 */
>          /* last cacheline: 60 bytes */
> };
> struct v4l2_buffer {
>          __u32                      index;                /*     0     4 */
>          __u32                      type;                 /*     4     4 */
>          __u32                      bytesused;            /*     8     4 */
>          __u32                      flags;                /*    12     4 */
>          __u32                      field;                /*    16     4 */
>          struct timeval             timestamp;            /*    20     8 */
>          struct v4l2_timecode       timecode;             /*    28    16 */
>          __u32                      sequence;             /*    44     4 */
>          __u32                      memory;               /*    48     4 */
>          union {
>                  __u32              offset;               /*    52     4 */
>                  long unsigned int  userptr;              /*    52     4 */
>                  struct v4l2_plane * planes;              /*    52     4 */
>                  __s32              fd;                   /*    52     4 */
>          } m;                                             /*    52     4 */
>          __u32                      length;               /*    56     4 */
>          __u32                      reserved2;            /*    60     4 */
>          /* --- cacheline 1 boundary (64 bytes) --- */
>          union {
>                  __s32              request_fd;           /*    64     4 */
>                  __u32              reserved;             /*    64     4 */
>          };                                               /*    64     4 */
> 
>          /* size: 68, cachelines: 2, members: 13 */
>          /* last cacheline: 4 bytes */
> };
> 
> The timestamp field is located at the same offset, so the
> v4l2_buffer_time64 structure definition should be fine on 32-bit
> platforms.

Right, on 32-bit x86, yes. I was wondering if this holds for other ABIs as well.
As far as I can see everything has a fixed size, apart from `long unsigned int  userptr`
and `struct v4l2_plane * planes`, but those should be pointer sized. I suppose maybe x32
has some room for complications, but I believe that is on its way out.

> 
>>> +};
>>> +
>>> +/*
>>> + * 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 */
>>
>> I'd do
>>    
>>     case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):
>>
>> instead of the comment.
>>
>>> +	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),
>>> @@ -875,6 +934,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;
>>> +	}
>>
>> I think it would be preferable to use the 64-bit version internally. And then
>> convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.
> 
> We would then need to fully define v4l2_buffer_time64 and use that
> internally, wouldn't we ? That is doable, but more complex. If we want
> to go that route, I would actually like to experiment with the
> suggestion I made in https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
> the compat layer with 64-bit time_t. That would give us a v4l2_buffer
> that uses 64-bit time_t internally, and the translation would only be
> required for applications that use 32-bit time_t. We could even consider
> that those are not worth supporting and avoid the translation
> completely. This should still solve Prasanth's issue if I'm not
> mistaken.

I'm in favor of the simpler solution, I just meant that if the whole thing
is implemented, it should handle edge cases and such.


> 
>> ---
>>
>> This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
>> Have you seen it?
>>
>>
>>> +
>>>    	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
>>>    		errno = ENOTTY;
>>>    		return -1;
>>> @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
>>>    		return -1;
>>>    	}
>>>    
>>> +	if (buf64)
>>> +		bufferToTime64(buf, buf64);
>>> +
>>>    	return ret;
>>>    }
>>>    
>
Laurent Pinchart Oct. 28, 2026, 9:37 a.m. UTC | #7
On Wed, Oct 28, 2026 at 10:09:13AM +0100, Barnabás Pőcze wrote:
> 2026. 10. 27. 23:55 keltezéssel, Laurent Pinchart írta:
> > On Fri, Oct 23, 2026 at 09:35:00PM +0200, Barnabás Pőcze wrote:
> >> 2026. 10. 23. 6:19 keltezéssel, Prasanth R írta:
> >>> 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 <ragaprasanth@gmail.com>
> >>> ---
> >>> Tested on a Raspberry Pi 4 with a 32-bit userspace built with 64-bit
> >>> time_t (the OpenEmbedded default for 32-bit Arm) and a UVC camera.
> >>>
> >>>    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 210c8e87..89e95198 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;
> > 
> > Wouldn't uint64_t be better than long long here ? glibc uses __int64 for
> > both (through __TIME64_T_TYPE and __SUSECONDS64_T_TYPE).
> > 
> >>> +	} timestamp;
> >>> +	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
> >>
> >> __u8 is a linux-ism, I think `std::byte` or even `unsigned char`, `uint8_t` would be preferable.
> > 
> > As this is a kernel structure it could be argued that kernel types are
> > appropriate, but I would only do so if we defined a full copy of the
> > structure (basically copying the struct v4l2_buffer32 definition from
> > drivers/media/v4l2-core/v4l2-compat-ioctl32.c).
> > 
> >> And is this appropriate for all platforms? Does `v4l2_buffer` have the same size with 64-bit
> >> on every architecture, can a `static_assert()` be added about the size?
> > 
> > Good idea.
> > 
> > 
> > Different but related issue, I've checked the structure layout in 32-bit
> > binaries with 32-bit and 64-bit time_t by compiling the following code
> > and using pahole.
> > 
> > --------
> > #include <linux/videodev2.h>
> > 
> > struct v4l2_buffer buf;
> > --------
> > 
> > $ gcc -m32 -g -U_FILE_OFFSET_BITS -D_FILE_OFFSET_BITS=32 -D_LARGEFILE64_SOURCE -U_TIME_BITS -c -o v4l2_buffer.o v4l2_buffer.c
> > $ pahole v4l2_buffer.o
> > struct timeval {
> >          __time_t                   tv_sec;               /*     0     4 */
> >          __suseconds_t              tv_usec;              /*     4     4 */
> > 
> >          /* size: 8, cachelines: 1, members: 2 */
> >          /* last cacheline: 8 bytes */
> > };
> > struct v4l2_timecode {
> >          __u32                      type;                 /*     0     4 */
> >          __u32                      flags;                /*     4     4 */
> >          __u8                       frames;               /*     8     1 */
> >          __u8                       seconds;              /*     9     1 */
> >          __u8                       minutes;              /*    10     1 */
> >          __u8                       hours;                /*    11     1 */
> >          __u8                       userbits[4];          /*    12     4 */
> > 
> >          /* size: 16, cachelines: 1, members: 7 */
> >          /* last cacheline: 16 bytes */
> > };
> > struct v4l2_plane {
> >          __u32                      bytesused;            /*     0     4 */
> >          __u32                      length;               /*     4     4 */
> >          union {
> >                  __u32              mem_offset;           /*     8     4 */
> >                  long unsigned int  userptr;              /*     8     4 */
> >                  __s32              fd;                   /*     8     4 */
> >          } m;                                             /*     8     4 */
> >          __u32                      data_offset;          /*    12     4 */
> >          __u32                      reserved[11];         /*    16    44 */
> > 
> >          /* size: 60, cachelines: 1, members: 5 */
> >          /* last cacheline: 60 bytes */
> > };
> > struct v4l2_buffer {
> >          __u32                      index;                /*     0     4 */
> >          __u32                      type;                 /*     4     4 */
> >          __u32                      bytesused;            /*     8     4 */
> >          __u32                      flags;                /*    12     4 */
> >          __u32                      field;                /*    16     4 */
> >          struct timeval             timestamp;            /*    20     8 */
> >          struct v4l2_timecode       timecode;             /*    28    16 */
> >          __u32                      sequence;             /*    44     4 */
> >          __u32                      memory;               /*    48     4 */
> >          union {
> >                  __u32              offset;               /*    52     4 */
> >                  long unsigned int  userptr;              /*    52     4 */
> >                  struct v4l2_plane * planes;              /*    52     4 */
> >                  __s32              fd;                   /*    52     4 */
> >          } m;                                             /*    52     4 */
> >          __u32                      length;               /*    56     4 */
> >          __u32                      reserved2;            /*    60     4 */
> >          /* --- cacheline 1 boundary (64 bytes) --- */
> >          union {
> >                  __s32              request_fd;           /*    64     4 */
> >                  __u32              reserved;             /*    64     4 */
> >          };                                               /*    64     4 */
> > 
> >          /* size: 68, cachelines: 2, members: 13 */
> >          /* last cacheline: 4 bytes */
> > };
> > 
> > $ gcc -m32 -g -c -o v4l2_buffer.o v4l2_buffer.c
> > $ pahole v4l2_buffer.o
> > struct timeval {
> >          __time_t                   tv_sec;               /*     0     4 */
> >          __suseconds_t              tv_usec;              /*     4     4 */
> > 
> >          /* size: 8, cachelines: 1, members: 2 */
> >          /* last cacheline: 8 bytes */
> > };
> > struct v4l2_timecode {
> >          __u32                      type;                 /*     0     4 */
> >          __u32                      flags;                /*     4     4 */
> >          __u8                       frames;               /*     8     1 */
> >          __u8                       seconds;              /*     9     1 */
> >          __u8                       minutes;              /*    10     1 */
> >          __u8                       hours;                /*    11     1 */
> >          __u8                       userbits[4];          /*    12     4 */
> > 
> >          /* size: 16, cachelines: 1, members: 7 */
> >          /* last cacheline: 16 bytes */
> > };
> > struct v4l2_plane {
> >          __u32                      bytesused;            /*     0     4 */
> >          __u32                      length;               /*     4     4 */
> >          union {
> >                  __u32              mem_offset;           /*     8     4 */
> >                  long unsigned int  userptr;              /*     8     4 */
> >                  __s32              fd;                   /*     8     4 */
> >          } m;                                             /*     8     4 */
> >          __u32                      data_offset;          /*    12     4 */
> >          __u32                      reserved[11];         /*    16    44 */
> > 
> >          /* size: 60, cachelines: 1, members: 5 */
> >          /* last cacheline: 60 bytes */
> > };
> > struct v4l2_buffer {
> >          __u32                      index;                /*     0     4 */
> >          __u32                      type;                 /*     4     4 */
> >          __u32                      bytesused;            /*     8     4 */
> >          __u32                      flags;                /*    12     4 */
> >          __u32                      field;                /*    16     4 */
> >          struct timeval             timestamp;            /*    20     8 */
> >          struct v4l2_timecode       timecode;             /*    28    16 */
> >          __u32                      sequence;             /*    44     4 */
> >          __u32                      memory;               /*    48     4 */
> >          union {
> >                  __u32              offset;               /*    52     4 */
> >                  long unsigned int  userptr;              /*    52     4 */
> >                  struct v4l2_plane * planes;              /*    52     4 */
> >                  __s32              fd;                   /*    52     4 */
> >          } m;                                             /*    52     4 */
> >          __u32                      length;               /*    56     4 */
> >          __u32                      reserved2;            /*    60     4 */
> >          /* --- cacheline 1 boundary (64 bytes) --- */
> >          union {
> >                  __s32              request_fd;           /*    64     4 */
> >                  __u32              reserved;             /*    64     4 */
> >          };                                               /*    64     4 */
> > 
> >          /* size: 68, cachelines: 2, members: 13 */
> >          /* last cacheline: 4 bytes */
> > };
> > 
> > The timestamp field is located at the same offset, so the
> > v4l2_buffer_time64 structure definition should be fine on 32-bit
> > platforms.
> 
> Right, on 32-bit x86, yes. I was wondering if this holds for other ABIs as well.
> As far as I can see everything has a fixed size, apart from `long unsigned int  userptr`
> and `struct v4l2_plane * planes`, but those should be pointer sized. I suppose maybe x32
> has some room for complications, but I believe that is on its way out.
> 
> >>> +};
> >>> +
> >>> +/*
> >>> + * 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 */
> >>
> >> I'd do
> >>    
> >>     case _IOWR('V', _IOC_NR(VIDIOC_QUERYBUF), struct v4l2_buffer_time64):
> >>
> >> instead of the comment.
> >>
> >>> +	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),
> >>> @@ -875,6 +934,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;
> >>> +	}
> >>
> >> I think it would be preferable to use the 64-bit version internally. And then
> >> convert the timestamp at the end, if it cannot be converted, return EOVERFLOW.
> > 
> > We would then need to fully define v4l2_buffer_time64 and use that
> > internally, wouldn't we ? That is doable, but more complex. If we want
> > to go that route, I would actually like to experiment with the
> > suggestion I made in https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> > of compiling only v4l2_compat.cpp with 32-bit time_t, and the rest of
> > the compat layer with 64-bit time_t. That would give us a v4l2_buffer
> > that uses 64-bit time_t internally, and the translation would only be
> > required for applications that use 32-bit time_t. We could even consider
> > that those are not worth supporting and avoid the translation
> > completely. This should still solve Prasanth's issue if I'm not
> > mistaken.
> 
> I'm in favor of the simpler solution, I just meant that if the whole thing
> is implemented, it should handle edge cases and such.

I agree. Hopefully we won't need the more complex option.

> >> ---
> >>
> >> This is also related to https://gitlab.freedesktop.org/camera/libcamera/-/work_items/290
> >> Have you seen it?
> >>
> >>> +
> >>>    	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
> >>>    		errno = ENOTTY;
> >>>    		return -1;
> >>> @@ -960,6 +1033,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
> >>>    		return -1;
> >>>    	}
> >>>    
> >>> +	if (buf64)
> >>> +		bufferToTime64(buf, buf64);
> >>> +
> >>>    	return ret;
> >>>    }
> >>>

Patch
diff mbox series

diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 210c8e87..89e95198 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),
@@ -875,6 +934,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;
@@ -960,6 +1033,9 @@  int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
 		return -1;
 	}
 
+	if (buf64)
+		bufferToTime64(buf, buf64);
+
 	return ret;
 }