linux V4L2框架分析

对于Linux内核里面的众多子系统,都采用了分层的思想来实现,V4L2子系统也不例外,跟framebuffer子系统类似似。V4L2子系统也分为两层。暂且分为核心层和硬件相关层。V4L2也是字符设备驱动程序,因此也脱离不了一般驱动程序的范围。

核心层:主要实现了字符设备驱动程序框架,使得驱动开发者不在关注字符设备驱动程序本身,只用关注硬件相关层即可,同事核心层也为上层APP提供了统一的访问接口,使得无论硬件如何变化,上层接口都能保持稳定。

硬件相关层:

以vivi.c为例分析V4L2框架:

module_init(vivi_init);
          ret = vivi_create_instance(i);
                dev = kzalloc(sizeof(*dev), GFP_KERNEL); //首先分配一个vivi_dev,
                ret = v4l2_device_register(NULL, &dev->v4l2_dev);//并不重要,只是初始化一些 
                                                                  自旋锁引用计数等
	        v4l2_ctrl_handler_init(hdl, 11); //初始化一些属性,这些属性在ioctl的时候会被用到
	        dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
		    	V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
	        dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			    V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
	        dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			    V4L2_CID_CONTRAST, 0, 255, 1, 16);
	        dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			    V4L2_CID_SATURATION, 0, 255, 1, 127);
	        dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			    V4L2_CID_HUE, -128, 127, 1, 0);
	        dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
	        dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
	        dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
	        dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
	        dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);
	        dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);

	        dev->v4l2_dev.ctrl_handler = hdl;

接着分配一个    struct video_device *vfd;,并设置它,最后注册video_device

	vfd = video_device_alloc();

	*vfd = vivi_template;
	vfd->debug = debug;
	vfd->v4l2_dev = &dev->v4l2_dev;
	set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
	vfd->lock = &dev->mutex;

	ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);

    vfd->v4l2_dev = &dev->v4l2_dev;

linux V4L2框架分析_第1张图片

其实video_register_device可以看作是核心层的一个接口,他为硬件相关层提供了统一的注册接口,将video_device注册进核心层。在上面设置了vfd指向vivi_template, vivi_fops中提供了很多的操作函数,这些操作函数最终会被传进核心层,最终会被应用层调用到。

static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
	.vidioc_querycap      = vidioc_querycap,
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
	.vidioc_reqbufs       = vidioc_reqbufs,
	.vidioc_querybuf      = vidioc_querybuf,
	.vidioc_qbuf          = vidioc_qbuf,
	.vidioc_dqbuf         = vidioc_dqbuf,
	.vidioc_s_std         = vidioc_s_std,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,
};

static struct video_device vivi_template = {
	.name		= "vivi",
	.fops           = &vivi_fops,
	.ioctl_ops 	= &vivi_ioctl_ops,
	.release	= video_device_release,

	.tvnorms              = V4L2_STD_525_60,
	.current_norm         = V4L2_STD_NTSC_M,
};

接着我们分析一下video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);

static inline int __must_check video_register_device(struct video_device *vdev,
		int type, int nr)
{
	return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}

int __video_register_device(struct video_device *vdev, int type, int nr,
		int warn_if_nr_in_use, struct module *owner)
{
	int i = 0;
	int ret;
	int minor_offset = 0;
	int minor_cnt = VIDEO_NUM_DEVICES;
	const char *name_base;

	vdev->minor = -1;

	/* v4l2_fh support */
	spin_lock_init(&vdev->fh_lock);
	INIT_LIST_HEAD(&vdev->fh_list);

	/* Part 1: check device type */
	switch (type) {
	case VFL_TYPE_GRABBER:
		name_base = "video"; //传入type参数为VFL_TYPE_GRABBER
		break;

	}

	vdev->vfl_type = type;
	vdev->cdev = NULL;
	if (vdev->v4l2_dev) {
		if (vdev->v4l2_dev->dev)
			vdev->parent = vdev->v4l2_dev->dev;
		if (vdev->ctrl_handler == NULL)
			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

		if (vdev->prio == NULL)
			vdev->prio = &vdev->v4l2_dev->prio;
	}

	switch (type) {
	case VFL_TYPE_GRABBER:
		minor_offset = 0;  
		minor_cnt = 64;
		break;

	for (i = 0; i < VIDEO_NUM_DEVICES; i++)
		if (video_device[i] == NULL)    //找出一个次设备号空项
			break;

	vdev->minor = i + minor_offset; //指定次设备号
	vdev->num = nr;
	devnode_set(vdev);

	/* Part 3: Initialize the character device */
	vdev->cdev = cdev_alloc();  //分配一个cdev ,这是字符设备驱动程序范畴

	vdev->cdev->ops = &v4l2_fops; //设置ops
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); //添加进内核

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &video_class;
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
	if (vdev->parent)
		vdev->dev.parent = vdev->parent;
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev); //注册设备

	/* Register the release callback that will be called when the last
	   reference to the device goes away. */
	vdev->dev.release = v4l2_device_release; //设置release函数


	/* Increase v4l2_device refcount */
	if (vdev->v4l2_dev)
		v4l2_device_get(vdev->v4l2_dev);

#if defined(CONFIG_MEDIA_CONTROLLER)
	/* Part 5: Register the entity. */
	if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
	    vdev->vfl_type != VFL_TYPE_SUBDEV) {
		vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
		vdev->entity.name = vdev->name;
		vdev->entity.v4l.major = VIDEO_MAJOR;
		vdev->entity.v4l.minor = vdev->minor;
		ret = media_device_register_entity(vdev->v4l2_dev->mdev,
			&vdev->entity);

}

video_device[vdev->minor] = vdev; //将vfd放入全局的video_device链表中

上面主要是核心层注册了一个字符设备。此时结构体间关系变为。

linux V4L2框架分析_第2张图片

其中v4l2_fops即为核心层提供给应用层的统一接口,应用层通过调用统一的接口,最终能访问到硬件相关层。

接下来分析vivi.c的open,read,write,ioctl过程

1. open
app:     open("/dev/video0",....)
---------------------------------------------------
drv:     v4l2_fops.v4l2_open
            vdev = video_devdata(filp);  // 根据次设备号从数组中得到video_device
            ret = vdev->fops->open(filp); //即上文中vivi_template.fops.open 即v4l2_fh_open
                        vivi_ioctl_ops.open
                            v4l2_fh_open

在上面中比较关键的一步是通过次设备号从 video_device数组中得到video_device。

struct video_device *video_devdata(struct file *file)
{
	return video_device[iminor(file->f_path.dentry->d_inode)];
}

 

2. read
app:    read ....
---------------------------------------------------
drv:    v4l2_fops.v4l2_read
            struct video_device *vdev = video_devdata(filp);
            ret = vdev->fops->read(filp, buf, sz, off);

3. ioctl
app:   ioctl
----------------------------------------------------
drv:   v4l2_fops.unlocked_ioctl
            v4l2_ioctl
                struct video_device *vdev = video_devdata(filp);
                ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                            video_ioctl2
                                video_usercopy(file, cmd, arg, __video_do_ioctl);
                                    __video_do_ioctl
                                        struct video_device *vfd = video_devdata(file);
                                        根据APP传入的cmd来获得、设置"某些属性"

v4l2_ctrl_handler的使用过程:
    __video_do_ioctl
        struct video_device *vfd = video_devdata(file);

         const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;

        case VIDIOC_QUERYCTRL:
        {

             struct v4l2_capability *cap = (struct v4l2_capability *)arg;

               ret = ops->vidioc_querycap(file, fh, cap); //即调用vivi_template.ioctl_ops.vidioc_querycap函数。

static int vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	struct vivi_dev *dev = video_drvdata(file);

	strcpy(cap->driver, "vivi");
	strcpy(cap->card, "vivi");
	strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
	cap->version = VIVI_VERSION;
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \
			    V4L2_CAP_READWRITE;
	return 0;
}

其他调用过程类似。

由此我们可以看出,核心层应经为我们做了很多事情,我们不必再去关心字符设备驱动程序,只用关系和硬件相关的东西,按照V4L2的框架写出硬件相关层即可。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

            

 

你可能感兴趣的:(V4L2)