V4L/DVB (4189): Add videodev support for VIDIOC_S/G/TRY_EXT_CTRLS.

videodev.c copies the control list specified in struct v4l2_ext_controls
to kernel space.

Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/videodev.c b/drivers/media/video/videodev.c
index 4315229..763e178 100644
--- a/drivers/media/video/videodev.c
+++ b/drivers/media/video/videodev.c
@@ -190,10 +190,15 @@
 	void    *mbuf = NULL;
 	void	*parg = NULL;
 	int	err  = -EINVAL;
+	int     is_ext_ctrl;
+	size_t  ctrls_size = 0;
+	void __user *user_ptr = NULL;
 
 #ifdef __OLD_VIDIOC_
 	cmd = video_fix_command(cmd);
 #endif
+	is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS ||
+		       cmd == VIDIOC_TRY_EXT_CTRLS);
 
 	/*  Copy arguments into temp kernel buffer  */
 	switch (_IOC_DIR(cmd)) {
@@ -215,19 +220,47 @@
 
 		err = -EFAULT;
 		if (_IOC_DIR(cmd) & _IOC_WRITE)
-			if (copy_from_user(parg, (void __user *)arg,
-							_IOC_SIZE(cmd)))
+			if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
 				goto out;
 		break;
 	}
+	if (is_ext_ctrl) {
+		struct v4l2_ext_controls *p = parg;
+
+		/* In case of an error, tell the caller that it wasn't
+		   a specific control that caused it. */
+		p->error_idx = p->count;
+		user_ptr = (void __user *)p->controls;
+		if (p->count) {
+			ctrls_size = sizeof(struct v4l2_ext_control) * p->count;
+			/* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */
+			mbuf = kmalloc(ctrls_size, GFP_KERNEL);
+			err = -ENOMEM;
+			if (NULL == mbuf)
+				goto out_ext_ctrl;
+			err = -EFAULT;
+			if (copy_from_user(mbuf, user_ptr, ctrls_size))
+				goto out_ext_ctrl;
+			p->controls = mbuf;
+		}
+	}
 
 	/* call driver */
 	err = func(inode, file, cmd, parg);
 	if (err == -ENOIOCTLCMD)
 		err = -EINVAL;
+	if (is_ext_ctrl) {
+		struct v4l2_ext_controls *p = parg;
+
+		p->controls = (void *)user_ptr;
+		if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size))
+			err = -EFAULT;
+		goto out_ext_ctrl;
+	}
 	if (err < 0)
 		goto out;
 
+out_ext_ctrl:
 	/*  Copy results into user buffer  */
 	switch (_IOC_DIR(cmd))
 	{
@@ -993,6 +1026,39 @@
 		ret=vfd->vidioc_s_ctrl(file, fh, p);
 		break;
 	}
+	case VIDIOC_G_EXT_CTRLS:
+	{
+		struct v4l2_ext_controls *p = arg;
+
+		if (vfd->vidioc_g_ext_ctrls) {
+			dbgarg(cmd, "count=%d\n", p->count);
+
+			ret=vfd->vidioc_g_ext_ctrls(file, fh, p);
+		}
+		break;
+	}
+	case VIDIOC_S_EXT_CTRLS:
+	{
+		struct v4l2_ext_controls *p = arg;
+
+		if (vfd->vidioc_s_ext_ctrls) {
+			dbgarg(cmd, "count=%d\n", p->count);
+
+			ret=vfd->vidioc_s_ext_ctrls(file, fh, p);
+		}
+		break;
+	}
+	case VIDIOC_TRY_EXT_CTRLS:
+	{
+		struct v4l2_ext_controls *p = arg;
+
+		if (vfd->vidioc_try_ext_ctrls) {
+			dbgarg(cmd, "count=%d\n", p->count);
+
+			ret=vfd->vidioc_try_ext_ctrls(file, fh, p);
+		}
+		break;
+	}
 	case VIDIOC_QUERYMENU:
 	{
 		struct v4l2_querymenu *p=arg;
@@ -1326,10 +1392,15 @@
 	void    *mbuf = NULL;
 	void	*parg = NULL;
 	int	err  = -EINVAL;
+	int     is_ext_ctrl;
+	size_t  ctrls_size = 0;
+	void __user *user_ptr = NULL;
 
 #ifdef __OLD_VIDIOC_
 	cmd = video_fix_command(cmd);
 #endif
+	is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS ||
+		       cmd == VIDIOC_TRY_EXT_CTRLS);
 
 	/*  Copy arguments into temp kernel buffer  */
 	switch (_IOC_DIR(cmd)) {
@@ -1356,13 +1427,43 @@
 		break;
 	}
 
+	if (is_ext_ctrl) {
+		struct v4l2_ext_controls *p = parg;
+
+		/* In case of an error, tell the caller that it wasn't
+		   a specific control that caused it. */
+		p->error_idx = p->count;
+		user_ptr = (void __user *)p->controls;
+		if (p->count) {
+			ctrls_size = sizeof(struct v4l2_ext_control) * p->count;
+			/* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */
+			mbuf = kmalloc(ctrls_size, GFP_KERNEL);
+			err = -ENOMEM;
+			if (NULL == mbuf)
+				goto out_ext_ctrl;
+			err = -EFAULT;
+			if (copy_from_user(mbuf, user_ptr, ctrls_size))
+				goto out_ext_ctrl;
+			p->controls = mbuf;
+		}
+	}
+
 	/* Handles IOCTL */
 	err = __video_do_ioctl(inode, file, cmd, parg);
 	if (err == -ENOIOCTLCMD)
 		err = -EINVAL;
+	if (is_ext_ctrl) {
+		struct v4l2_ext_controls *p = parg;
+
+		p->controls = (void *)user_ptr;
+		if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size))
+			err = -EFAULT;
+		goto out_ext_ctrl;
+	}
 	if (err < 0)
 		goto out;
 
+out_ext_ctrl:
 	/*  Copy results into user buffer  */
 	switch (_IOC_DIR(cmd))
 	{
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h
index dec6b24..a1b4731 100644
--- a/include/media/v4l2-dev.h
+++ b/include/media/v4l2-dev.h
@@ -232,6 +232,12 @@
 					struct v4l2_control *a);
 	int (*vidioc_s_ctrl)           (struct file *file, void *fh,
 					struct v4l2_control *a);
+	int (*vidioc_g_ext_ctrls)      (struct file *file, void *fh,
+					struct v4l2_ext_controls *a);
+	int (*vidioc_s_ext_ctrls)      (struct file *file, void *fh,
+					struct v4l2_ext_controls *a);
+	int (*vidioc_try_ext_ctrls)    (struct file *file, void *fh,
+					struct v4l2_ext_controls *a);
 	int (*vidioc_querymenu)        (struct file *file, void *fh,
 					struct v4l2_querymenu *a);