diff mbox

[v2,5/9,media] make VIDIOC_DQEVENT work with 64-bit time_t

Message ID 1442524780-781677-6-git-send-email-arnd@arndb.de (mailing list archive)
State New, archived
Headers show

Commit Message

Arnd Bergmann Sept. 17, 2015, 9:19 p.m. UTC
The v4l2 event queue uses a 'struct timespec' to pass monotonic
timestamps. This is not a problem by itself, but it breaks when user
space redefines timespec to use 'long long' on 32-bit systems.

This is the second approach on fixing the problem, by changing
the kernel to internally use 64-bit members for the timestamps
in struct v4l2_event, and letting user space use either version.

Unfortunately, we cannot use timespec64 here, because that does
not have a well-defined ABI yet, and differs from the 64-bit
version of 'timespec'. Using a pair of __s64 members makes sure
the structure is well-defined and contains no padding that might
leak kernel stack data.

We now need the handling for VIDIOC_DQEVENT32 on both 32-bit
and 64-bit architectures, so the handling for that is moved from
v4l2-compat-ioctl32.c to v4l2-ioctl.c and v4l2-subdev.c.

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
 drivers/media/v4l2-core/v4l2-compat-ioctl32.c | 35 ------------------
 drivers/media/v4l2-core/v4l2-event.c          | 35 +++++++++++++++---
 drivers/media/v4l2-core/v4l2-ioctl.c          | 53 ++++++++++++++++++++-------
 drivers/media/v4l2-core/v4l2-subdev.c         |  6 +++
 include/media/v4l2-event.h                    |  2 +
 include/uapi/linux/videodev2.h                | 31 ++++++++++++++++
 6 files changed, 107 insertions(+), 55 deletions(-)
diff mbox

Patch

diff --git a/drivers/media/v4l2-core/v4l2-compat-ioctl32.c b/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
index af635430524e..9ffe7302206e 100644
--- a/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
+++ b/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
@@ -735,32 +735,6 @@  static int put_v4l2_ext_controls32(struct v4l2_ext_controls *kp, struct v4l2_ext
 	return 0;
 }
 
-struct v4l2_event32 {
-	__u32				type;
-	union {
-		__u8			data[64];
-	} u;
-	__u32				pending;
-	__u32				sequence;
-	struct compat_timespec		timestamp;
-	__u32				id;
-	__u32				reserved[8];
-};
-
-static int put_v4l2_event32(struct v4l2_event *kp, struct v4l2_event32 __user *up)
-{
-	if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_event32)) ||
-		put_user(kp->type, &up->type) ||
-		copy_to_user(&up->u, &kp->u, sizeof(kp->u)) ||
-		put_user(kp->pending, &up->pending) ||
-		put_user(kp->sequence, &up->sequence) ||
-		compat_put_timespec(&kp->timestamp, &up->timestamp) ||
-		put_user(kp->id, &up->id) ||
-		copy_to_user(up->reserved, kp->reserved, 8 * sizeof(__u32)))
-			return -EFAULT;
-	return 0;
-}
-
 struct v4l2_edid32 {
 	__u32 pad;
 	__u32 start_block;
@@ -814,7 +788,6 @@  static int put_v4l2_edid32(struct v4l2_edid *kp, struct v4l2_edid32 __user *up)
 #define VIDIOC_G_EXT_CTRLS32    _IOWR('V', 71, struct v4l2_ext_controls32)
 #define VIDIOC_S_EXT_CTRLS32    _IOWR('V', 72, struct v4l2_ext_controls32)
 #define VIDIOC_TRY_EXT_CTRLS32  _IOWR('V', 73, struct v4l2_ext_controls32)
-#define	VIDIOC_DQEVENT32	_IOR ('V', 89, struct v4l2_event32)
 #define VIDIOC_CREATE_BUFS32	_IOWR('V', 92, struct v4l2_create_buffers32)
 #define VIDIOC_PREPARE_BUF32	_IOWR('V', 93, struct v4l2_buffer32)
 
@@ -860,7 +833,6 @@  static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 	case VIDIOC_G_EXT_CTRLS32: cmd = VIDIOC_G_EXT_CTRLS; break;
 	case VIDIOC_S_EXT_CTRLS32: cmd = VIDIOC_S_EXT_CTRLS; break;
 	case VIDIOC_TRY_EXT_CTRLS32: cmd = VIDIOC_TRY_EXT_CTRLS; break;
-	case VIDIOC_DQEVENT32: cmd = VIDIOC_DQEVENT; break;
 	case VIDIOC_OVERLAY32: cmd = VIDIOC_OVERLAY; break;
 	case VIDIOC_STREAMON32: cmd = VIDIOC_STREAMON; break;
 	case VIDIOC_STREAMOFF32: cmd = VIDIOC_STREAMOFF; break;
@@ -940,9 +912,6 @@  static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 		err = get_v4l2_ext_controls32(&karg.v2ecs, up);
 		compatible_arg = 0;
 		break;
-	case VIDIOC_DQEVENT:
-		compatible_arg = 0;
-		break;
 	}
 	if (err)
 		return err;
@@ -983,10 +952,6 @@  static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 		err = put_v4l2_framebuffer32(&karg.v2fb, up);
 		break;
 
-	case VIDIOC_DQEVENT:
-		err = put_v4l2_event32(&karg.v2ev, up);
-		break;
-
 	case VIDIOC_G_EDID:
 	case VIDIOC_S_EDID:
 		err = put_v4l2_edid32(&karg.v2edid, up);
diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c
index 8d3171c6bee8..149342490e91 100644
--- a/drivers/media/v4l2-core/v4l2-event.c
+++ b/drivers/media/v4l2-core/v4l2-event.c
@@ -92,6 +92,28 @@  int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
 }
 EXPORT_SYMBOL_GPL(v4l2_event_dequeue);
 
+int v4l2_event_dequeue32(struct v4l2_fh *fh, struct v4l2_event32 *event32,
+		       int nonblocking)
+{
+	struct v4l2_event event64;
+	int ret;
+
+	ret = v4l2_event_dequeue(fh, &event64, nonblocking);
+	if (ret)
+		return ret;
+
+	/* only the timestamp differs, so use memcpy to copy everything else */
+	memcpy(event32, &event64, offsetof(struct v4l2_event32, timestamp));
+	event32->timestamp.tv_sec = (long)event64.timestamp.tv_sec;
+	event32->timestamp.tv_nsec = (long)event64.timestamp.tv_nsec;
+	memcpy(&event32->id, &event64.id,
+	       sizeof(event32->id) + sizeof(event32->reserved));
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_dequeue32);
+
+
 /* Caller must hold fh->vdev->fh_lock! */
 static struct v4l2_subscribed_event *v4l2_event_subscribed(
 		struct v4l2_fh *fh, u32 type, u32 id)
@@ -108,7 +130,7 @@  static struct v4l2_subscribed_event *v4l2_event_subscribed(
 }
 
 static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
-		const struct timespec *ts)
+		const struct timespec64 *ts)
 {
 	struct v4l2_subscribed_event *sev;
 	struct v4l2_kevent *kev;
@@ -156,7 +178,8 @@  static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *e
 	if (copy_payload)
 		kev->event.u = ev->u;
 	kev->event.id = ev->id;
-	kev->event.timestamp = *ts;
+	kev->event.timestamp.tv_sec = ts->tv_sec;
+	kev->event.timestamp.tv_nsec = ts->tv_nsec;
 	kev->event.sequence = fh->sequence;
 	sev->in_use++;
 	list_add_tail(&kev->list, &fh->available);
@@ -170,12 +193,12 @@  void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
 {
 	struct v4l2_fh *fh;
 	unsigned long flags;
-	struct timespec timestamp;
+	struct timespec64 timestamp;
 
 	if (vdev == NULL)
 		return;
 
-	ktime_get_ts(&timestamp);
+	ktime_get_ts64(&timestamp);
 
 	spin_lock_irqsave(&vdev->fh_lock, flags);
 
@@ -189,9 +212,9 @@  EXPORT_SYMBOL_GPL(v4l2_event_queue);
 void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev)
 {
 	unsigned long flags;
-	struct timespec timestamp;
+	struct timespec64 timestamp;
 
-	ktime_get_ts(&timestamp);
+	ktime_get_ts64(&timestamp);
 
 	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
 	__v4l2_event_queue_fh(fh, ev, &timestamp);
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 4a384fc765b8..7aab18dd2ca5 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -797,22 +797,18 @@  static void v4l_print_frmivalenum(const void *arg, bool write_only)
 	}
 }
 
-static void v4l_print_event(const void *arg, bool write_only)
+static void v4l_print_event_data(const void *arg, bool write_only, u32 type)
 {
-	const struct v4l2_event *p = arg;
-	const struct v4l2_event_ctrl *c;
+	const struct v4l2_event_vsync *vsync = arg;
+	const struct v4l2_event_ctrl *c = arg;
+	const struct v4l2_event_frame_sync *frame_sync = arg;
 
-	pr_cont("type=0x%x, pending=%u, sequence=%u, id=%u, "
-		"timestamp=%lu.%9.9lu\n",
-			p->type, p->pending, p->sequence, p->id,
-			p->timestamp.tv_sec, p->timestamp.tv_nsec);
-	switch (p->type) {
+	switch (type) {
 	case V4L2_EVENT_VSYNC:
 		printk(KERN_DEBUG "field=%s\n",
-			prt_names(p->u.vsync.field, v4l2_field_names));
+			prt_names(vsync->field, v4l2_field_names));
 		break;
 	case V4L2_EVENT_CTRL:
-		c = &p->u.ctrl;
 		printk(KERN_DEBUG "changes=0x%x, type=%u, ",
 			c->changes, c->type);
 		if (c->type == V4L2_CTRL_TYPE_INTEGER64)
@@ -825,12 +821,34 @@  static void v4l_print_event(const void *arg, bool write_only)
 			c->step, c->default_value);
 		break;
 	case V4L2_EVENT_FRAME_SYNC:
-		pr_cont("frame_sequence=%u\n",
-			p->u.frame_sync.frame_sequence);
+		pr_cont("frame_sequence=%u\n", frame_sync->frame_sequence);
 		break;
 	}
 }
 
+static void v4l_print_event32(const void *arg, bool write_only)
+{
+	const struct v4l2_event32 *p = arg;
+	pr_cont("type=0x%x, pending=%u, sequence=%u, id=%u, "
+			"timestamp=%d.%9.9u\n",
+			p->type, p->pending, p->sequence, p->id,
+			p->timestamp.tv_sec, p->timestamp.tv_nsec);
+
+	return v4l_print_event_data(&p->u, write_only, p->type);
+}
+
+static void v4l_print_event64(const void *arg, bool write_only)
+{
+	const struct v4l2_event *p = arg;
+
+	pr_cont("type=0x%x, pending=%u, sequence=%u, id=%u, "
+			"timestamp=%lld.%9.9llu\n",
+			p->type, p->pending, p->sequence, p->id,
+			p->timestamp.tv_sec, p->timestamp.tv_nsec);
+
+	return v4l_print_event_data(&p->u, write_only, p->type);
+}
+
 static void v4l_print_event_subscription(const void *arg, bool write_only)
 {
 	const struct v4l2_event_subscription *p = arg;
@@ -2235,12 +2253,18 @@  static int v4l_dbg_g_chip_info(const struct v4l2_ioctl_ops *ops,
 #endif
 }
 
-static int v4l_dqevent(const struct v4l2_ioctl_ops *ops,
+static int v4l_dqevent64(const struct v4l2_ioctl_ops *ops,
 				struct file *file, void *fh, void *arg)
 {
 	return v4l2_event_dequeue(fh, arg, file->f_flags & O_NONBLOCK);
 }
 
+static int v4l_dqevent32(const struct v4l2_ioctl_ops *ops,
+				struct file *file, void *fh, void *arg)
+{
+	return v4l2_event_dequeue32(fh, arg, file->f_flags & O_NONBLOCK);
+}
+
 static int v4l_subscribe_event(const struct v4l2_ioctl_ops *ops,
 				struct file *file, void *fh, void *arg)
 {
@@ -2449,7 +2473,8 @@  static struct v4l2_ioctl_info v4l2_ioctls[] = {
 	IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO),
 	IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO),
 	IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0),
-	IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0),
+	IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent64, v4l_print_event64, 0),
+	IOCTL_INFO_FNC(VIDIOC_DQEVENT32, v4l_dqevent32, v4l_print_event32, 0),
 	IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0),
 	IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0),
 	IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE),
diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c
index 83615b8fb46a..9b0cde143c6c 100644
--- a/drivers/media/v4l2-core/v4l2-subdev.c
+++ b/drivers/media/v4l2-core/v4l2-subdev.c
@@ -211,6 +211,12 @@  static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg)
 	case VIDIOC_TRY_EXT_CTRLS:
 		return v4l2_try_ext_ctrls(vfh->ctrl_handler, arg);
 
+	case VIDIOC_DQEVENT32:
+		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS))
+			return -ENOIOCTLCMD;
+
+		return v4l2_event_dequeue32(vfh, arg, file->f_flags & O_NONBLOCK);
+
 	case VIDIOC_DQEVENT:
 		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_EVENTS))
 			return -ENOIOCTLCMD;
diff --git a/include/media/v4l2-event.h b/include/media/v4l2-event.h
index 9792f906423b..5e8ce27a0671 100644
--- a/include/media/v4l2-event.h
+++ b/include/media/v4l2-event.h
@@ -124,6 +124,8 @@  struct v4l2_subscribed_event {
 
 int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
 		       int nonblocking);
+int v4l2_event_dequeue32(struct v4l2_fh *fh, struct v4l2_event32 *event,
+		       int nonblocking);
 void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev);
 void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev);
 int v4l2_event_pending(struct v4l2_fh *fh);
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 3228fbebcd63..3e2c497c31fd 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -2082,10 +2082,40 @@  struct v4l2_event {
 	} u;
 	__u32				pending;
 	__u32				sequence;
+#ifdef __KERNEL__
+	struct {
+		__s64			tv_sec;
+		__s64			tv_nsec;
+	} timestamp;
+#else
 	struct timespec			timestamp;
+#endif
+	__u32				id;
+	__u32				reserved[8];
+};
+
+#ifdef __KERNEL__
+/*
+ * User space will see either 64-bit or 32-bit time_t, which changes
+ * the v4l2_event layout. Both are y2038 safe because the timestamps
+ * are in monotonic time, but the kernel has to handle both cases.
+ */
+struct v4l2_event32 {
+	__u32				type;
+	union {
+		__u8			data[64];
+	} u;
+	__u32				pending;
+	__u32				sequence;
+	struct {
+		__s32			tv_sec;
+		__s32			tv_nsec;
+	}				timestamp;
+
 	__u32				id;
 	__u32				reserved[8];
 };
+#endif
 
 #define V4L2_EVENT_SUB_FL_SEND_INITIAL		(1 << 0)
 #define V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK	(1 << 1)
@@ -2237,6 +2267,7 @@  struct v4l2_create_buffers {
 #define	VIDIOC_S_DV_TIMINGS	_IOWR('V', 87, struct v4l2_dv_timings)
 #define	VIDIOC_G_DV_TIMINGS	_IOWR('V', 88, struct v4l2_dv_timings)
 #define	VIDIOC_DQEVENT		 _IOR('V', 89, struct v4l2_event)
+#define	VIDIOC_DQEVENT32	 _IOR('V', 89, struct v4l2_event32)
 #define	VIDIOC_SUBSCRIBE_EVENT	 _IOW('V', 90, struct v4l2_event_subscription)
 #define	VIDIOC_UNSUBSCRIBE_EVENT _IOW('V', 91, struct v4l2_event_subscription)