[pvrusb2] [RFCv1 PATCH] Expose controls in debugfs

Hans Verkuil hverkuil at xs4all.nl
Fri Mar 2 10:17:54 CST 2012


On Friday, March 02, 2012 14:36:16 Hans Verkuil wrote:
> Hi all,
> 
> I've resumed work on something that was discussed ages ago: exposing controls
> in debugfs. It's quite useful in pvrusb2, but that code is completely custom.
> Creating a general method for this is the goal of this patch.
> 
> The old thread can be found here:
> 
> http://www.mail-archive.com/linux-media@vger.kernel.org/msg17453.html
> 
> Although the original proposal in that thread was to do it in sysfs, the
> overall preference was to use debugfs instead.
> 
> This patch is quite simple and does not touch any driver code. All drivers
> that use the V4L2 control framework (which sadly does not include pvrusb2
> at the moment) will get it for free.
> 
> It even implements poll(), so you can poll on a control waiting for the value
> to change. It also honours the priority setting (VIDIOC_G/S_PRIORITY).
> 
> The control type information is not exposed. As mentioned in the thread above,
> I think adding debugfs entries with the type information will make it very messy.
> 
> One possible idea is to have the control files support VIDIOC_QUERYCTRL and
> VIDIOC_QUERYMENU and to create a small utility that will read out that
> information and output it in a manner that can be easily parsed. It would be
> trivial to do this.
> 
> This patch should also work fine for v4l2-subdevX nodes, although I don't
> have the hardware at the moment to check this.
> 
> The git tree containing this patch is here:
> 
> http://git.linuxtv.org/hverkuil/media_tree.git/shortlog/refs/heads/debugfs
> 
> This patch only exposes controls, it does not add controls that emulate V4L2
> ioctls (as pvrusb2 does). I would have to experiment with that a bit. I'm not
> convinced it is that useful (as opposed to calling v4l2-ctl).

OK, here is a patch that implements a v4l2_input control as a front-end for
VIDIOC_G/S_INPUT and a v4l2_frequency control as a front-end for G/S_FREQUENCY.

This code doesn't touch any drivers, it is all done by the V4L2 core.

Right now the code that handles these two controls is part of v4l2-ctrls.c.
If we decide to go ahead with this, then this will be moved to a separate
source and it will also be put under a config option.

So for the vivi driver you have the following if debugfs is mounted under
/sys/kernel/debug:

$ l /sys/kernel/debug/video4linux/video1/controls/
bitmask  brightness  contrast  gain_automatic  integer_32_bits  menu        string      volume
boolean  button      gain      hue             integer_64_bits  saturation  v4l2_input

For ivtv it will look like this:

$ l /sys/kernel/debug/video4linux/video0/controls/
balance                           mpeg_insert_navigation_packets     mpeg_video_bitrate
brightness                        mpeg_median_chroma_filter_maximum  mpeg_video_bitrate_mode
chroma_agc                        mpeg_median_chroma_filter_minimum  mpeg_video_decoder_frame_count
chroma_gain                       mpeg_median_filter_type            mpeg_video_decoder_pts
contrast                          mpeg_median_luma_filter_maximum    mpeg_video_encoding
hue                               mpeg_median_luma_filter_minimum    mpeg_video_gop_closure
mpeg_audio_crc                    mpeg_spatial_chroma_filter_type    mpeg_video_gop_size
mpeg_audio_emphasis               mpeg_spatial_filter                mpeg_video_mute
mpeg_audio_encoding               mpeg_spatial_filter_mode           mpeg_video_mute_yuv
mpeg_audio_layer_ii_bitrate       mpeg_spatial_luma_filter_type      mpeg_video_peak_bitrate
mpeg_audio_multilingual_playback  mpeg_stream_type                   mpeg_video_temporal_decimation
mpeg_audio_mute                   mpeg_stream_vbi_format             mute
mpeg_audio_playback               mpeg_temporal_filter               saturation
mpeg_audio_sampling_frequency     mpeg_temporal_filter_mode          v4l2_frequency
mpeg_audio_stereo_mode            mpeg_video_aspect                  v4l2_input
mpeg_audio_stereo_mode_extension  mpeg_video_b_frames                volume

It's magic :-)

As a proof of concept this shows that it is quite easy to do this cleanly.
Whether this is actually something that should be followed-up is another
question.

Comments are welcome!

Regards,

	Hans

Signed-off-by: Hans Verkuil <hans.verkuil at cisco.com>
---
 drivers/media/video/v4l2-ctrls.c |  164 ++++++++++++++++++++++++++++++--------
 drivers/media/video/v4l2-dev.c   |    5 +
 include/media/v4l2-ctrls.h       |    2 +
 include/media/v4l2-dev.h         |    4 +
 4 files changed, 142 insertions(+), 33 deletions(-)

diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c
index 4f49643..3cef2e2 100644
--- a/drivers/media/video/v4l2-ctrls.c
+++ b/drivers/media/video/v4l2-ctrls.c
@@ -2408,6 +2408,8 @@ EXPORT_SYMBOL(v4l2_ctrl_poll);
 static const char *get_class_prefix(u32 id)
 {
 	switch (V4L2_CTRL_ID2CLASS(id)) {
+	case 0:
+		return "v4l2_";
 	case V4L2_CTRL_CLASS_USER:
 		return "";
 	case V4L2_CTRL_CLASS_CAMERA:
@@ -2436,6 +2438,82 @@ static void make_debugfs_name(char *src)
 	*dst = 0;
 }
 
+#define DEBUGFS_CID_INPUT	1
+#define DEBUGFS_CID_FREQUENCY	2
+
+static void debugfs_v4l2_read(struct file *filp, struct video_device *vdev,
+		struct v4l2_ctrl *ctrl)
+{
+	switch (ctrl->id) {
+	case DEBUGFS_CID_INPUT:
+		vdev->ioctl_ops->vidioc_g_input(filp, filp->private_data,
+				&ctrl->cur.val);
+		break;
+
+	case DEBUGFS_CID_FREQUENCY: {
+		struct v4l2_frequency freq;
+
+		freq.tuner = 0;
+		freq.type = (vdev->vfl_type == VFL_TYPE_RADIO) ?
+			V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+		memset(freq.reserved, 0, sizeof(freq.reserved));
+		vdev->ioctl_ops->vidioc_g_frequency(filp, filp->private_data,
+				&freq);
+		ctrl->cur.val = freq.frequency;
+		break;
+	}
+	}
+}
+
+static int debugfs_v4l2_write(struct file *filp, struct video_device *vdev,
+		struct v4l2_ctrl *ctrl)
+{
+	switch (ctrl->id) {
+	case DEBUGFS_CID_INPUT:
+		return vdev->ioctl_ops->vidioc_s_input(filp, filp->private_data,
+				ctrl->cur.val);
+
+	case DEBUGFS_CID_FREQUENCY: {
+		struct v4l2_frequency freq;
+
+		freq.tuner = 0;
+		freq.type = (vdev->vfl_type == VFL_TYPE_RADIO) ?
+			V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+		freq.frequency = ctrl->cur.val;
+		memset(freq.reserved, 0, sizeof(freq.reserved));
+		return vdev->ioctl_ops->vidioc_s_frequency(filp, filp->private_data,
+				&freq);
+	}
+	}
+	return -EINVAL;
+}
+
+int v4l2_ctrl_debugfs_v4l2_controls(struct video_device *vdev)
+{
+	struct v4l2_ctrl_config cfg = {
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.max = INT_MAX,
+		.step = 1,
+	};
+	int ret = v4l2_ctrl_handler_init(&vdev->v4l2_hdl, 1);
+
+	if (ret)
+		return ret;
+	if (vdev->ioctl_ops->vidioc_enum_input) {
+		cfg.id = DEBUGFS_CID_INPUT;
+		cfg.name = "input";
+		if (!v4l2_ctrl_new_custom(&vdev->v4l2_hdl, &cfg, NULL))
+			return -ENOMEM;
+	}
+	if (vdev->ioctl_ops->vidioc_s_frequency) {
+		cfg.id = DEBUGFS_CID_FREQUENCY;
+		cfg.name = "frequency";
+		if (!v4l2_ctrl_new_custom(&vdev->v4l2_hdl, &cfg, NULL))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
 static ssize_t debugfs_read(struct file *filp, char __user *buf,
 		size_t sz, loff_t *off)
 {
@@ -2454,6 +2532,9 @@ static ssize_t debugfs_read(struct file *filp, char __user *buf,
 	if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)
 		return -EACCES;
 
+	if (ctrl->handler == &vdev->v4l2_hdl)
+		debugfs_v4l2_read(filp, vdev, ctrl);
+
 	v4l2_ctrl_lock(ctrl);
 	/* g_volatile_ctrl will update the current control values */
 	if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) {
@@ -2602,6 +2683,8 @@ static ssize_t debugfs_write(struct file *filp, const char __user *buf,
 	}
 	ret = try_or_set_cluster(NULL, master, true);
 	v4l2_ctrl_unlock(ctrl);
+	if (!ret && ctrl->handler == &vdev->v4l2_hdl)
+		ret = debugfs_v4l2_write(filp, vdev, ctrl);
 	return ret ? ret : sz;
 }
 
@@ -2611,7 +2694,8 @@ static int debugfs_open(struct inode *inode, struct file *filp)
 	struct v4l2_ctrl_ref *ref = filp->f_path.dentry->d_inode->i_private;
 	struct video_device *vdev = video_devdata(filp);
 
-	if (!ret && test_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags)) {
+	if (!ret && ref->ctrl->handler != &vdev->v4l2_hdl &&
+			test_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags)) {
 		struct v4l2_fh *fh = filp->private_data;
 		struct v4l2_event_subscription sub = {
 			.type = V4L2_EVENT_CTRL,
@@ -2632,7 +2716,7 @@ static unsigned int debugfs_poll(struct file *file, struct poll_table_struct *wa
 	struct video_device *vdev = video_devdata(file);
 	unsigned int mask = 0;
 
-	if (test_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags)) {
+	if (ctrl->handler != &vdev->v4l2_hdl && test_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags)) {
 		mask = v4l2_ctrl_poll(file, wait);
 
 		if (mask & POLLPRI) {
@@ -2659,6 +2743,44 @@ const struct file_operations debugfs_fops = {
 	.llseek = no_llseek,
 };
 
+
+static int v4l2_ctrl_debugfs_add_control(struct video_device *vdev,
+		struct v4l2_ctrl_ref *ref)
+{
+	struct v4l2_ctrl *ctrl = ref->ctrl;
+	const char *prefix = get_class_prefix(ctrl->id);
+	char name[strlen(ctrl->name) + strlen(prefix) + 1];
+	mode_t mode = S_IRUGO | S_IWUGO;
+	struct dentry *debugfs_file;
+
+	if (ctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS)
+		return 0;
+	if (ctrl->flags & V4L2_CTRL_FLAG_DISABLED)
+		return 0;
+
+	strcpy(name, prefix);
+	strcat(name, ctrl->name);
+	make_debugfs_name(name);
+
+	if (ref->minor < 0)
+		ref->minor = vdev->minor;
+
+	if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)
+		mode = S_IWUGO;
+	else if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
+		mode = S_IRUGO;
+
+	debugfs_file = debugfs_create_file(name,
+			mode, vdev->debugfs_controls, ref,
+			&debugfs_fops);
+	if (IS_ERR_OR_NULL(debugfs_file)) {
+		printk(KERN_ERR "%s: debugfs: could not create %s\n",
+				__func__, name);
+		return -ENODEV;
+	}
+	return 0;
+}
+
 /* Update the debugfs controls if something changed. */
 int v4l2_ctrl_debugfs_update_controls(struct video_device *vdev)
 {
@@ -2681,38 +2803,14 @@ int v4l2_ctrl_debugfs_update_controls(struct video_device *vdev)
 	}
 
 	list_for_each_entry(ref, &hdl->ctrl_refs, node) {
-		struct v4l2_ctrl *ctrl = ref->ctrl;
-		const char *prefix = get_class_prefix(ctrl->id);
-		char name[strlen(ctrl->name) + strlen(prefix) + 1];
-		mode_t mode = S_IRUGO | S_IWUGO;
-		struct dentry *debugfs_file;
-
-		if (ctrl->type == V4L2_CTRL_TYPE_CTRL_CLASS)
-			continue;
-		if (ctrl->flags & V4L2_CTRL_FLAG_DISABLED)
-			continue;
-
-		strcpy(name, prefix);
-		strcat(name, ctrl->name);
-		make_debugfs_name(name);
-
-		if (ref->minor < 0)
-			ref->minor = vdev->minor;
-
-		if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)
-			mode = S_IWUGO;
-		else if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
-			mode = S_IRUGO;
-
-		debugfs_file = debugfs_create_file(name,
-				mode, vdev->debugfs_controls, ref,
-				&debugfs_fops);
-		if (IS_ERR_OR_NULL(debugfs_file)) {
-			printk(KERN_ERR "%s: debugfs: could not create %s\n",
-					__func__, name);
-			err = -ENODEV;
+		err = v4l2_ctrl_debugfs_add_control(vdev, ref);
+		if (err)
+			break;
+	}
+	list_for_each_entry(ref, &vdev->v4l2_hdl.ctrl_refs, node) {
+		err = v4l2_ctrl_debugfs_add_control(vdev, ref);
+		if (err)
 			break;
-		}
 	}
 	if (err) {
 		debugfs_remove_recursive(vdev->debugfs_controls);
diff --git a/drivers/media/video/v4l2-dev.c b/drivers/media/video/v4l2-dev.c
index 0a59a75..3050d2e 100644
--- a/drivers/media/video/v4l2-dev.c
+++ b/drivers/media/video/v4l2-dev.c
@@ -701,6 +701,9 @@ int __video_register_device(struct video_device *vdev, int type, int nr,
 					__func__, video_device_node_name(vdev));
 			goto cleanup;
 		}
+		ret = v4l2_ctrl_debugfs_v4l2_controls(vdev);
+		if (ret)
+			goto cleanup;
 		ret = v4l2_ctrl_debugfs_update_controls(vdev);
 		if (ret)
 			goto cleanup;
@@ -753,6 +756,7 @@ cleanup:
 		debugfs_remove_recursive(vdev->debugfs_devnode);
 		vdev->debugfs_devnode = NULL;
 		vdev->debugfs_controls = NULL;
+		v4l2_ctrl_handler_free(&vdev->v4l2_hdl);
 	}
 	mutex_lock(&videodev_lock);
 	if (vdev->cdev)
@@ -781,6 +785,7 @@ void video_unregister_device(struct video_device *vdev)
 	debugfs_remove_recursive(vdev->debugfs_devnode);
 	vdev->debugfs_devnode = NULL;
 	vdev->debugfs_controls = NULL;
+	v4l2_ctrl_handler_free(&vdev->v4l2_hdl);
 	mutex_lock(&videodev_lock);
 	/* This must be in a critical section to prevent a race with v4l2_open.
 	 * Once this bit has been cleared video_get may never be called again.
diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h
index 54c0fcf..9634ce8 100644
--- a/include/media/v4l2-ctrls.h
+++ b/include/media/v4l2-ctrls.h
@@ -524,6 +524,8 @@ int v4l2_s_ext_ctrls(struct v4l2_fh *fh, struct v4l2_ctrl_handler *hdl,
    Returns 0 if vdev->ctrl_handler == NULL. */
 int v4l2_ctrl_debugfs_update_controls(struct video_device *vdev);
 
+int v4l2_ctrl_debugfs_v4l2_controls(struct video_device *vdev);
+
 
 /* Helpers for subdevices. If the associated ctrl_handler == NULL then they
    will all return -EINVAL. */
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h
index 6f21386..df1872d 100644
--- a/include/media/v4l2-dev.h
+++ b/include/media/v4l2-dev.h
@@ -17,6 +17,7 @@
 #include <linux/videodev2.h>
 
 #include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
 
 #define VIDEO_MAJOR	81
 
@@ -94,6 +95,9 @@ struct video_device
 	/* Control handler associated with this device node. May be NULL. */
 	struct v4l2_ctrl_handler *ctrl_handler;
 
+	/* Control handler containing controls that map to V4L2 ioctls. */
+	struct v4l2_ctrl_handler v4l2_hdl;
+
 	/* debugfs directories */
 	struct dentry *debugfs_devnode;
 	struct dentry *debugfs_controls;
-- 
1.7.9.1



More information about the pvrusb2 mailing list